home *** CD-ROM | disk | FTP | other *** search
/ The 640 MEG Shareware Studio 2 / The 640 Meg Shareware Studio CD-ROM Volume II (Data Express)(1993).ISO / utility / clkdev14.zip / CLOCKDEV.ASM < prev   
Assembly Source File  |  1991-07-24  |  118KB  |  4,228 lines

  1. ;**********************************************************************
  2. ;* Copyright (C) 1988, 1989, 1990, 1991, Alan P. Barrett, Durban.     *
  3. ;*                                      *
  4. ;* Permission is granted to use, copy or distribute this software in  *
  5. ;* any way, except for profit.  Modified versions of this software    *
  6. ;* may be distributed, provided that this notice is retained and the  *
  7. ;* modifications are clearly indicated.                               *
  8. ;*                                      *
  9. ;* No liability is accepted for any loss or damage whatsoever caused  *
  10. ;* or allegedly caused by the use or misuse of this software.          *
  11. ;**********************************************************************
  12.  
  13. ;***********************************************************************
  14. ;* File: CLOCKDEV.ASM
  15. ;* Author: A.P. Barrett
  16. ;* Date created: 06 May 1988
  17. ;* Date edited:  24 Jul 1991
  18. ;* Purpose: MS-DOS installable device driver, for CLOCK$ device.
  19. ;*    On an AT: Makes some DOS date and time accesses use the AT BIOS
  20. ;*        real time clock.  (The AT clock has a 1 second granularity,
  21. ;*        which is too poor to be used for all time accesses.)
  22. ;*    On a PC with a National Semiconductors MM58167AN clock chip:
  23. ;*        Makes all DOS date and time accesses use the chip.
  24. ;*    On a PC with an MSM6242 clock chip:  Makes some DOS date and
  25. ;*        time accesses use the chip.  (The chip has a 1 second
  26. ;*        granularity.)
  27. ;*    On a PC with no timer chip, fixes the date change bug present in
  28. ;*        MSDOS 3.2 (and possibly other DOS versions).
  29. ;* Instal this device driver by placing
  30. ;*        DEVICE=CLOCK.DEV
  31. ;*    in the CONFIG.SYS file.  (Some additional options might be required;
  32. ;*    see the help message that is printed on startup.)
  33. ;* Compile as follows:
  34. ;*        MASM CLOCKDEV ;
  35. ;*        LINK CLOCKDEV ;
  36. ;*        EXE2BIN CLOCKDEV.EXE CLOCK.DEV
  37. ;*        DEL CLOCKDEV.EXE
  38. ;*        DEL CLOCKDEV.OBJ
  39. ;*
  40. ;* Please send comments, bug fixes, complaints etc. to the author:
  41. ;*     Alan Barrett, Department of Electronic Engineering, University of
  42. ;*     Natal, Durban, 4001, South Africa.
  43. ;* RFC822 email address: barrett@ee.und.ac.za
  44. ;***********************************************************************
  45.  
  46. ; Reasons for using CLOCK.DEV:
  47. ;
  48. ;     If you have an AT, you probably have to run the SETUP program
  49. ;     whenever the date or time needs to be changed.  The SETUP
  50. ;     program's insistance on rebooting the computer has led to the
  51. ;     availability of several small utilities that set the real time
  52. ;     clock with DOS's idea of the date and time, so instead of running
  53. ;     SETUP you can first use the DOS date and time commands, and then
  54. ;     run some special utility to update the real time clock.  Using
  55. ;     CLOCK.DEV will make the DOS date and time commands set the real
  56. ;     time clock, without running any other programs.
  57. ;
  58. ;     If you have a PC (or XT) with a clock chip, then you probably
  59. ;     have a special program to read and set the time.  The program may
  60. ;     be called TIMER, or there may be two programs called SETCLOCK and
  61. ;     GETCLOCK (or some other combination).  You have to use a special
  62. ;     command to update the time maintained by the card, because the
  63. ;     DOS date and time commands do not do that.  Another problem with
  64. ;     some of these clock cards is that the year does not change
  65. ;     correctly.  Using CLOCK.DEV will make the DOS date and time
  66. ;     commands set the real time clock, without running any other
  67. ;     programs, and will ensure that the year changes correctly at the
  68. ;     beginning of January.
  69. ;
  70. ;     If you use MS-DOS version 3.2, the DOS date will not change at
  71. ;     midnight.  Using CLOCK.DEV will fix this problem, even on a PC
  72. ;     without a clock chip.  (Some other DOS versions might also have
  73. ;     this problem.)
  74. ;
  75. ;     If you leave your PC turned on but idle for more than one day (so
  76. ;     that the time passes midnight more than once), then the date will
  77. ;     not be correct; the date will move forward by only one day,
  78. ;     instead of by more than one day.  If your PC has a clock chip,
  79. ;     using CLOCK.DEV will correct this problem.  If you do not have a
  80. ;     clock chip, there is no way of correcting the problem without
  81. ;     modifying the BIOS.
  82. ;
  83. ; How to use CLOCK.DEV:
  84. ;
  85. ;     Simply add the line
  86. ;      DEVICE=\CLOCK.DEV
  87. ;     to your CONFIG.SYS file.  (Include a drive and directory name if
  88. ;     CLOCK.DEV is not stored in the root directory.) When your
  89. ;     computer is booted up, it will determine whether you have an AT
  90. ;     or an add-on clock chip or neither, and will act appropriately.
  91. ;     Although the built in default action will usually be suitable,
  92. ;     there are some options that can be specified in the DEVICE=...
  93. ;     line; try using "DEVICE=CLOCK.DEV -H" to get a list of options,
  94. ;     or just look for the message in the source code.
  95.  
  96. ;***********************************************************************
  97.  
  98. ; Modification history:
  99. ;
  100. ; Ver 1.2   28 May 1988
  101. ;        First release.  Supports AT BIOS clock, supports add-on
  102. ;        National Semiconductor MM58167AN clock chip for XTs,
  103. ;        fixes MS-DOS version 3.2 date change bug.
  104. ;        15 Jun 1988
  105. ;        Fixed bug in conversion from date (year, month and day)
  106. ;        to day number (since 01-Jan-1980).  Comment said "BH is
  107. ;        still zero", but of course it wasn't.
  108. ;        07 Oct 1988
  109. ;        Improved error reporting when there is no clock chip.
  110. ;        There are now two separate messages, depending on
  111. ;        whether user said "-C+" or"-C=xxxx".
  112. ;        20 Jun 1989
  113. ;        When time is first established at bootup, and later when
  114. ;        time is changed, be sure to set plain BIOS time,
  115. ;        even if a clock chip is used.  This is desirable because
  116. ;        some software uses the plain BIOS time instead of the
  117. ;        DOS time.  (Plain BIOS time is INT 1Ah function 0 and 1.
  118. ;        It is not to be confused with AT BIOS time, which is
  119. ;        INT 1Ah function 2, 3, 4 and 5.)
  120. ;        20 Mar 1990
  121. ;        When the date changes, the BIOS will report the date change
  122. ;        to the first program that asks for the plain BIOS time,
  123. ;        but that might be another program, other than this one.
  124. ;        In such a case, the fact that the date has changed will be
  125. ;        hidden from us.  But we can partially fix the problem by
  126. ;        checking if the BIOS time ever seems to run backwards,
  127. ;        and assuming a date change if that does happen.
  128. ;
  129. ; Ver 1.3   04 Sep 1990
  130. ;        Added support for MSM6242 clock chip.  Changed command line
  131. ;        options slightly.  Restructured some code to make
  132. ;        support for other clock chips a little easier to add.
  133. ;        (Memory usage increased from 1488 to 1680 bytes.)
  134. ;        05 Sep 1990
  135. ;        MSM6242 returns junk in high 4 bits of each port, so ignore
  136. ;        that.
  137. ;
  138. ; Ver 1.4   24 Jul 1991
  139. ;        Slight cleanup.  No new functionality.
  140.  
  141. ;***********************************************************************
  142.  
  143. ; Discussion:
  144. ;
  145. ; On the IBM PC, the 8253 timer divides a 1.19318 Mhz clock by 65536 to
  146. ; generate a time-base interrupt on IRQ0, which corresponds to 8088
  147. ; hardware interrupt number 8.  The BIOS uses this interrupt to increment
  148. ; a 32-bit counter, in order to keep track of the time-of-day.  The BIOS
  149. ; also uses the time-base interrupt to handle diskette motor timeouts,
  150. ; and performs an INT 1Ch to allow user written routines to be invoked
  151. ; once for each time-base interrupt.
  152. ;
  153. ; When the BIOS time-of-day count reaches 1573040, it is reset to zero
  154. ; and an overflow flag is set.  The next time the BIOS time-of-day
  155. ; service (interrupt 1Ah function 0) is called, the overflow flag is
  156. ; reset, and is returned to the caller.  This is a signal to the caller
  157. ; of the BIOS time-of-day service that the date has changed.
  158. ;
  159. ; If the date changes more than once before the BIOS time-of-day service
  160. ; is called, there is no way for the caller to know this.  Thus if a
  161. ; machine is left idle for several days, the date will change only once.
  162. ; This behaviour is unpleasant, but cannot be changed without modifying
  163. ; the BIOS or intercepting either the time-base interrupt or the user
  164. ; tick interrupt.
  165. ;
  166. ; MS-DOS interrupt 21h functions 2Ah, 2Bh, 2Ch and 2Dh are used to get
  167. ; and set the date and time.  MS-DOS implements these functions via a
  168. ; device driver whose attributes indicate that it is a clock device.
  169. ; This device is usually named CLOCK$.  Reading and writing the clock
  170. ; device gets and sets the date and time.  The clock device presumably
  171. ; relies on the BIOS time-of-day service for the time, and maintains its
  172. ; own record of the current date.  When the BIOS indicates that the date
  173. ; has changed, the DOS updates its idea of the current date.
  174. ;
  175. ; Versions of MS-DOS up to 3.1 behaved as just described, but MS-DOS
  176. ; version 3.2 does not ever increment the current date.  This behaviour
  177. ; is entirely unacceptable, because at midnight the time changes but the
  178. ; date does not.  If the date stamps on files are used to keep track of
  179. ; which files are newer than others, then files modified just after
  180. ; midnight will appear to be OLDER than files modified just before
  181. ; midnight.
  182. ;
  183. ; The IBM-PC AT (and compatibles) contains a built-in real time clock,
  184. ; which is automatically read when the machine is booted up.  The AT BIOS
  185. ; provides functions to read and set the real time clock.  (BIOS interrupt
  186. ; 1Ah, functions 2, 3, 4 and 5.) Changing the time in the real time clock
  187. ; is usually done only by the SETUP program.  The clock has a resolution
  188. ; of 1 second.
  189. ;
  190. ; Many users instal peripheral cards containing real time clock chips
  191. ; that continue to function when the power is turned off.  These cards
  192. ; are usually supplied together with programs to change their idea of
  193. ; the date and time, and to make the DOS idea of the date and time
  194. ; match the card's idea of the date and time.  The clock chip is usually
  195. ; consulted only when the computer is booted up.  A problem with many
  196. ; clock chips (or rather, with the programs supplied to control the
  197. ; chips) is that the year may not change correctly.
  198. ;
  199. ; This installable device driver is a replacement for the DOS clock
  200. ; device, and is an attempt to solve the following problems:
  201. ;
  202. ;      The DOS date and time must never go backwards.  When the BIOS
  203. ;      indicates that the date has changed, THE DATE MUST CHANGE.
  204. ;        (The MS-DOS 3.2 date change bug is fixed by this program.)
  205. ;
  206. ;      The real time clock maintained by the BIOS on an AT computer
  207. ;      (and usually accessible only via the SETUP program) should
  208. ;      be used for all DOS date and time functions.
  209. ;
  210. ;      The AT BIOS clock has a one-second resolution.  This is
  211. ;      improved by usually reading the ordinary BIOS timer, and only
  212. ;      using the AT BIOS clock when the date changes or after a long
  213. ;      idle period, or when the user changes the date or time.
  214. ;
  215. ;      If there is a real time clock on a peripheral card, the DOS
  216. ;      date and time should match that on the peripheral card.
  217. ;
  218. ;      If there is a real time clock on a peripheral card, the year
  219. ;      should change correctly.  The software provided with some
  220. ;      such add-on clock chips doesn't work properly.
  221. ;
  222. ;      The date should change the correct number of times if the
  223. ;      computer is left unattended for several days.
  224. ;        (Not fixed yet, for machines without clock chips.)
  225. ;
  226.  
  227. ;***********************************************************************
  228.  
  229. ;
  230. ; These definitions control the conditional assembly
  231. ;
  232.  
  233. False = 0
  234. True = not False
  235.  
  236. ;
  237. ; Make do_debug_writes True to get lots of additional output that
  238. ; may assist in debugging.  The macros that actually perform the
  239. ; debugging output are disabled if do_debug_writes is False.
  240. ;
  241.  
  242. do_debug_writes = False
  243.  
  244. ;
  245. ; Make add_test_code True to include additional code for testing.
  246. ;
  247.  
  248. add_test_code = False
  249.  
  250. ;
  251. ; It may be necessary to switch to a local stack.  This is because
  252. ; DOS does not guarantee to have more than 40 free words of stack
  253. ; space when the device driver is called.  Even the 40 words mentioned
  254. ; in the Programmer's Reference is probably not guaranteed.
  255. ; This driver uses about 30 words of stack space if the debug output
  256. ; is disabled, so use_local_stack can be set to False.  ????
  257. ; When debugging output is performed, much more stack space is used,
  258. ; so use_local_stack should be True, with a reasonably large
  259. ; local_stack_size.
  260. ; Note that the size of the local stack is specified in BYTES.
  261. ; Remember that any interrupts occuring while this program is busy
  262. ; will need space on the stack.
  263. ;
  264.  
  265. if do_debug_writes
  266.  use_local_stack = True   ; Strongly recommend this be set to TRUE
  267.  local_stack_size = 300
  268. else
  269.  use_local_stack = True   ; Try TRUE if FALSE gives problems
  270.  local_stack_size = 160
  271. endif
  272.  
  273. ;***********************************************************************
  274.  
  275. ;
  276. ; Define the order of segments in memory
  277. ;
  278. ; All the segments go together in a GROUP, so the whole thing will
  279. ; actually be just one 64k segment.
  280. ;
  281. ; The reason for defining multiple segments here is so that the source code
  282. ; can be written in any order, and the linker will place everything in the
  283. ; correct order in the executable file.  We particularly want to ensure
  284. ; that non-resident code and data appears after the resident code and data,
  285. ; so that its space can be freed after initialisation.    We must also
  286. ; ensure that the header comes first in memory.
  287. ;
  288.  
  289. header_segment    segment byte
  290. header_segment    ends
  291. cgroup    group header_segment
  292.  
  293. resident_data    segment byte
  294. resident_data    ends
  295. cgroup    group resident_data
  296.  
  297. resident_code    segment byte
  298. resident_code    ends
  299. cgroup    group resident_code
  300.  
  301. if use_local_stack
  302. local_stack_seg segment word
  303.         ;;; db '-->'
  304.         db (local_stack_size+7)/8 dup (' STACK  ')
  305.         even         ; Ensure word alignment
  306. local_stack_top label word
  307.         ;;; db '<--'
  308. local_stack_seg ends
  309. cgroup    group local_stack_seg
  310. endif ; use_local_stack
  311.  
  312. startup_data    segment byte
  313. end_resident_memory label byte     ; This must be the start of the section
  314.                  ; that is used only at startup time
  315. startup_data    ends
  316. cgroup    group startup_data
  317.  
  318. startup_code    segment byte
  319. startup_code    ends
  320. cgroup    group startup_code
  321.  
  322. identity_seg    segment byte
  323. identity_seg    ends
  324. cgroup    group identity_seg
  325.  
  326. ;
  327. ; CS will point to the CGROUP segment while the code is running.
  328. ;
  329.         assume cs:cgroup
  330.  
  331. ;***********************************************************************
  332.  
  333. ;
  334. ; This is the message printed at initialisation.  We arrange for the
  335. ; message to be the last thing in the executable file so that it
  336. ; is easy to find.
  337. ;
  338.  
  339. identity_seg    segment
  340. start_message label byte
  341.     db 'CLOCKDEV version 1.4 [24 Jul 1991].  Copyright (C) A.P. Barrett',13,10
  342. start_message_len    equ  $-start_message
  343. identity_seg    ends
  344.  
  345. ;***********************************************************************
  346.  
  347. ;
  348. ; Some useful macro definitions
  349. ;
  350.  
  351. ;
  352. ; Push all registers except CS, IP, SS and SP.
  353. ;
  354. save_regs   macro
  355.         pushf
  356.         push ax
  357.         push bx
  358.         push cx
  359.         push dx
  360.         push es
  361.         push ds
  362.         push si
  363.         push di
  364.         push bp
  365.         endm
  366.  
  367. ;
  368. ; Pop all registers saved by the save_regs macro
  369. ;
  370. restore_regs macro
  371.         pop  bp
  372.         pop  di
  373.         pop  si
  374.         pop  ds
  375.         pop  es
  376.         pop  dx
  377.         pop  cx
  378.         pop  bx
  379.         pop  ax
  380.         popf
  381.         endm
  382.  
  383. ;
  384. ; JCXNZ: jump if CX is non zero.
  385. ;
  386. jcxnz        macro dest
  387.         local l1
  388.     jcxz    l1
  389.     jmp  short dest
  390.     l1:
  391.         endm
  392.  
  393. ;
  394. ; Conditional jump, where the target is too far away.
  395. ; Use, for example, <jcond je, dest> instead of <je dest>.
  396. ;
  397. ; There must be a better way of doing this ???
  398. ;
  399. jcond        macro type,dest
  400.         local next, l1
  401.   ifidni <type>, <jcxz>      ; CX register zero
  402.     jcxz    l1
  403.     jmp    short next
  404.   endif
  405.   ifidni <type>, <jcxnz>   ; CX register non zero (really a macro)
  406.     jcxz    next
  407.   endif
  408.   ifidni <type>, <jc>      ; carry flag set
  409.     jnc    next
  410.   endif
  411.   ifidni <type>, <jnc>      ; carry flag clear
  412.     jc    next
  413.   endif
  414.   ifidni <type>, <jz>      ; zero flag set
  415.     jnz    next
  416.   endif 
  417.   ifidni <type>, <jnz>      ; zero flag clear
  418.     jz    next
  419.   endif 
  420.   ifidni <type>, <jo>      ; overflow flag set
  421.     jno    next
  422.   endif 
  423.   ifidni <type>, <jno>      ; overflow flag clear
  424.     jo    next
  425.   endif 
  426.   ifidni <type>, <js>      ; sign flag set (negative)
  427.     jns    next
  428.   endif 
  429.   ifidni <type>, <jns>      ; sign flag clear (positive)
  430.     js    next
  431.   endif 
  432.   ifidni <type>, <jp>      ; parity flag set (even parity)
  433.     jnp    next
  434.   endif 
  435.   ifidni <type>, <jnp>      ; parity flag clear (odd parity)
  436.     jp    next
  437.   endif 
  438.   ifidni <type>, <jpe>      ; even parity
  439.     jpo    next
  440.   endif 
  441.   ifidni <type>, <jpo>      ; odd parity
  442.     jpe    next
  443.   endif 
  444.   ifidni <type>, <je>      ; equal
  445.     jne    next
  446.   endif 
  447.   ifidni <type>, <jl>      ; less (signed)
  448.     jnl    next
  449.   endif 
  450.   ifidni <type>, <jg>      ; greater (signed)
  451.     jng    next
  452.   endif 
  453.   ifidni <type>, <jb>      ; below (unsigned)
  454.     jnb    next
  455.   endif 
  456.   ifidni <type>, <ja>      ; above (unsigned)
  457.     jna    next
  458.   endif 
  459.   ifidni <type>, <jle>      ; less or equal (signed)
  460.     jnle    next
  461.   endif 
  462.   ifidni <type>, <jge>      ; greater or equal (signed)
  463.     jnge    next
  464.   endif 
  465.   ifidni <type>, <jbe>      ; below or equal (unsigned)
  466.     jnbe next
  467.   endif 
  468.   ifidni <type>, <jae>      ; above or equal (unsigned)
  469.     jnae    next
  470.   endif 
  471.   ifidni <type>, <jne>      ; not equal
  472.     je    next
  473.   endif 
  474.   ifidni <type>, <jnl>      ; not less (signed)
  475.     jl    next
  476.   endif 
  477.   ifidni <type>, <jng>      ; not greater (signed)
  478.     jg    next
  479.   endif 
  480.   ifidni <type>, <jnb>      ; not below (unsigned)
  481.     jb    next
  482.   endif 
  483.   ifidni <type>, <jna>      ; not above (unsigned)
  484.     ja    next
  485.   endif 
  486.   ifidni <type>, <jnle>      ; not less or equal (signed)
  487.     jle    next
  488.   endif 
  489.   ifidni <type>, <jnge>      ; not greater or equal (signed)
  490.     jge    next
  491.   endif 
  492.   ifidni <type>, <jnbe>      ; not below or equal (unsigned)
  493.     jbe  next
  494.   endif 
  495.   ifidni <type>, <jnae>      ; not above or equal (unsigned)
  496.     jae    next
  497.   endif 
  498.      l1:    jmp    dest
  499.      next:
  500.          endm
  501. ;
  502. ; If debugging is disabled, the following macros are defined as do-nothing.
  503. ; If debugging is enabled, they are defined in the normal way.
  504. ;
  505. if do_debug_writes
  506.  
  507. ;
  508. ; Display a message for debugging
  509. ;
  510. debug_message macro    msg
  511.         local m, m_len
  512. ; The message must appear in memory somewhere
  513. resident_data segment
  514. m        db   msg
  515. m_len        equ  $-m
  516. resident_data ends
  517. ; Save registers
  518.         push cx
  519.         push si
  520.         push ds
  521. ; Print the message
  522.         push cs
  523.         pop  ds
  524.         mov  si,offset cgroup:m
  525.         mov  cx,m_len
  526.         call display_message
  527. ; Restore registers
  528.         pop  ds
  529.         pop  si
  530.         pop  cx
  531.         endm
  532.  
  533. ;
  534. ; Display a character (for debugging)
  535. ;
  536. debug_show_char macro chr
  537. ; Save regs
  538.         pushf
  539.         push ax
  540.         push bx
  541. ; Display
  542.         mov  ah,0Eh
  543.         mov  al,chr
  544.         mov  bx,1
  545.         int  10h
  546. ; Restore regs
  547.         pop  bx
  548.         pop  ax
  549.         popf
  550.         endm
  551.  
  552. ;
  553. ; Display a byte in hexadecimal (for debugging)
  554. ;
  555. debug_hex_byte macro byt
  556.         push ax
  557.         mov  al,byt
  558.         call display_hex_byte
  559.         pop  ax
  560.         endm
  561.  
  562. ;
  563. ; Display a string of hex bytes, starting at seg:addr (for debugging)
  564. ;
  565. debug_hex_string macro seg,addr,count,count_size
  566.         push cx
  567.         push si
  568.         push ds
  569.         push seg
  570.         pop  ds
  571.         lea  si,addr
  572. ifidni <count_size>, <byte>
  573.         mov  cl,count
  574.         xor  ch,ch
  575. else
  576.         mov  cx,count
  577. endif
  578.         call display_hex_bytes
  579.         pop  ds
  580.         pop  si
  581.         pop  cx
  582.         endm
  583. ;
  584. ; Display a word in hexadecimal (for debugging)
  585. ;
  586. debug_hex_word macro wrd
  587.         push ax
  588.         mov  ax,wrd
  589.         xchg al,ah
  590.         call display_hex_byte
  591.         mov  al,ah
  592.         call display_hex_byte
  593.         pop  ax
  594.         endm
  595.  
  596. ;
  597. ; Display a DWORD in hexadecimal.  THE VALUE MUST BE IN CX_DX.  For debugging.
  598. ;
  599. debug_hex_cx_dx macro
  600.         push ax
  601.         mov  al,ch
  602.         call display_hex_byte
  603.         mov  al,cl
  604.         call display_hex_byte
  605.         mov  al,dh
  606.         call display_hex_byte
  607.         mov  al,dl
  608.         call display_hex_byte
  609.         pop  ax
  610.         endm
  611.  
  612. else ; do_debug_writes
  613. ;
  614. ; Empty definitions for the debug macros, if debugging is turned off.
  615. ;
  616. debug_message    macro msg
  617.         endm
  618. debug_show_char macro chr
  619.         endm
  620. debug_hex_byte    macro byt
  621.         endm
  622. debug_hex_string macro seg,addr,count,count_size
  623.         endm
  624. debug_hex_word    macro wrd
  625.         endm
  626. debug_hex_cx_dx macro
  627.         endm
  628. endif ; do_debug_writes
  629.  
  630. ;***********************************************************************
  631.  
  632. ;
  633. ; Equates for DOS device driver attributes
  634. ;
  635.  
  636. D_ATT_chr    equ  1000000000000000b        ; Character device
  637. D_ATT_blk    equ  0000000000000000b        ; Block device (chr bit is off)
  638. D_ATT_ioc    equ  0100000000000000b        ; Supports IOCTL
  639. D_ATT_oub    equ  0010000000000000b        ; Output until busy (char only)
  640. D_ATT_fat    equ  0010000000000000b        ; Uses FATID byte (block only)
  641. D_ATT_opn    equ  0000100000000000b        ; Understands Open/Close
  642. D_ATT_3_2    equ  0000000001000000b        ; Supports DOS 3.2 functions
  643. D_ATT_clk    equ  0000000000001000b        ; This is the clock device
  644. D_ATT_nul    equ  0000000000000100b        ; This is the NUL device
  645. D_ATT_sto    equ  0000000000000010b        ; This is the std. output device
  646. D_ATT_sti    equ  0000000000000001b        ; This is the std. input device
  647.  
  648. ;
  649. ; Equates for offsets of various items within driver requests
  650. ;
  651.  
  652. ; Items in the request header
  653. D_REQ_len    equ  0                ; Length of the request block
  654. D_REQ_unit    equ  1                ; Device subunit number
  655. D_REQ_command    equ  2                ; Command code number
  656. D_REQ_status    equ  3                ; Returned status code
  657.  
  658. ; Additional items in the INIT request
  659. D_INIT_end    equ  14             ; Points to end of resident part
  660. D_INIT_args    equ  18             ; Points to command line arguments
  661.  
  662. ; Additional items in read, write and IOCTL requests
  663. D_RDWR_media    equ  13             ; Media byte from BPB
  664. D_RDWR_buffer    equ  14             ; Address of transfer buffer
  665. D_RDWR_length    equ  18             ; Number of chars or sectors
  666. D_RDWR_startsec equ  20             ; Start sector num. (block device)
  667.  
  668. ; Additional item in the non-destructive read request
  669. D_PEEK_char    equ  13             ; Returned character
  670.  
  671. ;
  672. ; Equates for request completion status codes.
  673. ; These are all byte values, to be stored in the high byte of the
  674. ; status word.
  675. ;
  676.  
  677. D_STAT_error    equ  10000000b            ; Error
  678. D_STAT_busy    equ  00000010b            ; Device busy
  679. D_STAT_done    equ  00000001b            ; Request complete
  680.  
  681. ;
  682. ; Equates for request error codes.
  683. ; These are to be stored in the low byte of the status word when the
  684. ; ERROR bit is set in the high byte.
  685. ;
  686.  
  687. D_ERR_write_prot     equ 0            ; Write protect violation
  688. D_ERR_bad_unit         equ 1            ; Unknown unit number
  689. D_ERR_not_ready      equ 2            ; Device not ready
  690. D_ERR_bad_command    equ 3            ; Unknown command
  691. D_ERR_CRC         equ 4            ; CRC error
  692. D_ERR_bad_length     equ 5            ; Bad request structure length
  693. D_ERR_seek         equ 6            ; Seek error
  694. D_ERR_unknown_media  equ 7            ; Unknown media
  695. D_ERR_sector         equ 8            ; Sector not found
  696. D_ERR_paper         equ 9            ; Printer out of paper
  697. D_ERR_write         equ 0Ah            ; Write fault
  698. D_ERR_read         equ 0Bh            ; Read fault
  699. D_ERR_general         equ 0Ch            ; General failure
  700. D_ERR_bad_change     equ 0Fh            ; Invalid disk change
  701.  
  702. ;***********************************************************************
  703.  
  704. ;
  705. ; CODE STARTS HERE
  706. ;
  707.  
  708. header_segment    segment
  709.         assume ds:nothing, es:nothing
  710.  
  711. ;
  712. ; First, we need a special header so that DOS recognises the driver correctly.
  713. ;
  714.  
  715. CLK_header label byte
  716.         dw   -1,-1            ; List linkage filled in when DOS
  717.                         ; installs the device driver
  718.         dw   D_ATT_chr+D_ATT_clk    ; Attribute word
  719.         dw   offset cgroup:CLK_strategy  ; STRATEGY entry point
  720.         dw   offset cgroup:CLK_interrupt ; "INTERRUPT" entry point
  721.         db   'CLOCK$   '        ; Name, must be 8 chars
  722.  
  723. ;
  724. ; For debugging only:  Something to search for in memory to help find
  725. ; where the driver has been loaded.
  726. ; When not debugging, comment this out to save a few bytes of memory.
  727. ;
  728. ;;;        db   'APB'
  729.  
  730. header_segment    ends
  731.  
  732. ;***********************************************************************
  733.  
  734. ;
  735. ; Data used by the resident portion of the CLOCK$ device driver.
  736. ;
  737.  
  738. resident_data    segment
  739.  
  740. ;
  741. ; Pointer to the header of the current device request.
  742. ;
  743. request_pointer dd   ?
  744.  
  745. if use_local_stack
  746. ;
  747. ; Saved values needed for switching to a local stack
  748. ;
  749. saved_ax    dw   ?
  750. saved_ss    dw   ?
  751. saved_sp    dw   ?
  752. endif ; use_local_stack
  753.  
  754. ;
  755. ; The current date and time.
  756. ; This is stored in the format required by the DOS, so a READ or WRITE
  757. ; is accomplished by copying the six-byte data block either to or from here.
  758. ;
  759. ; The order of the following data definitions is important.
  760. ;
  761. CLK_date_time_len equ 6          ; Length of the date and time data
  762. CLK_date_time label byte
  763. CLK_date_days      dw   0         ; Days since 01-Jan-1980
  764. CLK_time_min      db   0         ; Current time: minutes
  765. CLK_time_hour      db   0         ; Current time: hour
  766. CLK_time_hsec      db   0         ; Current time: hundredths of secs
  767. CLK_time_sec      db   0         ; Current time: seconds
  768.  
  769. ;
  770. ; Some more date values, used internally by various procedures.
  771. ;
  772. CLK_date_century  db   0         ; First two digits of year
  773. CLK_date_year      db   0         ; Last two digits of year
  774. CLK_date_month      db   0         ; Current month
  775. CLK_date_day      db   0         ; Day of month
  776. CLK_date_weekday  db   0         ; Day of week (0 = Sunday)
  777.  
  778. ;
  779. ; This command table is used by the CLOCK$ device "interrupt" handler.
  780. ; Each word is a pointer to a routine that handles a particular function.
  781. ;
  782. CLK_command_table label byte
  783.         dw   offset cgroup:CLK_init     ; 0: Instal the device driver
  784.         dw   offset cgroup:bad_command     ; 1: Media check (block dev.)
  785.         dw   offset cgroup:bad_command     ; 2: Build BPB (block dev.)
  786.         dw   offset cgroup:bad_command     ; 3: IOCTL input
  787.         dw   offset cgroup:CLK_input     ; 4: Input (get date, time)
  788.         dw   offset cgroup:CLK_peek     ; 5: Non destr. read no wait
  789.         dw   offset cgroup:exit_ok     ; 6: Input status query
  790.         dw   offset cgroup:exit_ok     ; 7: Input flush
  791.         dw   offset cgroup:CLK_output     ; 8: Output (set date, time)
  792.         dw   offset cgroup:CLK_output     ; 9: Output with verify
  793.         dw   offset cgroup:exit_ok     ;10: Output status query
  794.         dw   offset cgroup:exit_ok     ;11: Output flush
  795.  
  796. CLK_last_command  equ 11
  797.         ; all other function codes are treated as bad commands
  798.  
  799. ;
  800. ; The timer chip base address is determined during initialisation.
  801. ;
  802. CLK_chip_IO_base    dw     0FFFFh     ; I/O base address for clock chip.
  803.         ; Default value FFFF means look in all usual places.
  804.         ; Value zero means do not use chip.
  805.         ; Other value is place to look.
  806.  
  807. ;
  808. ; This pointer specifies a subroutine to be called by the
  809. ; CLK_fix_our_time procedure.  The pointer is set by the
  810. ; startup code.
  811. ;
  812. CLK_get_procedure_ptr        dw ?
  813.  
  814. ;
  815. ; This pointer specifies a subroutine to be called by the
  816. ; CLK_set_other_timers procedure.  The pointer is set by the
  817. ; startup code.
  818. ;
  819. CLK_set_procedure_ptr        dw ?
  820.  
  821. ;
  822. ; When a coarse grain clock chip is used, CLK_get_procedure_ptr
  823. ; will point to CLK_get_BIOS_or_other_time, and this pointer
  824. ; will point to a subroutine that will get the time from the
  825. ; 'other' source.  This is set by the startup code.
  826. ;
  827. CLK_get_other_procedure_ptr dw ?
  828.  
  829. ;
  830. ; These words are used for temporary storage of intermediate results.
  831. ;
  832. t1    dw    ?
  833. t2    dw    ?
  834. t3    dw    ?
  835. t4    dw    ?
  836. t5    dw    ?
  837.  
  838. resident_data    ends
  839.  
  840. ;***********************************************************************
  841.  
  842. ;
  843. ; A null procedure.  May be pointed to by CLK_set_procedure_ptr.
  844. ;
  845.  
  846. resident_code    segment
  847. null_proc    proc near
  848.         ret
  849. null_proc    endp
  850. resident_code    ends
  851.  
  852. ;***********************************************************************
  853.  
  854. ;
  855. ; The strategy entry point is called by DOS to pass the request block.
  856. ; The driver simply saves the address of the request block and returns.
  857. ; On multi-tasking systems, the driver should add the request to a queue.
  858. ;
  859.  
  860. resident_code    segment
  861.  
  862. CLK_strategy proc far
  863. ;
  864. ; ES:BX points to the request block.  Just save the address and return
  865. ;
  866.         mov  word ptr cs:[request_pointer],bx
  867.         mov  word ptr cs:[request_pointer+2],es
  868.         ret                ; FAR return
  869.  
  870. CLK_strategy endp
  871.  
  872. resident_code    ends
  873.  
  874. ;***********************************************************************
  875.  
  876. ;
  877. ; The interrupt entry point handles the request that was saved by the
  878. ; strategy entry point.  DOS calls it immediately after the strategy
  879. ; routine returns.
  880. ;
  881.  
  882. resident_code    segment
  883.  
  884. CLK_interrupt proc far
  885. if use_local_stack
  886. ;
  887. ; Switch to a local stack, in case the stack is too small.
  888. ;
  889.         mov  cs:[saved_ax],ax
  890.         mov  ax,sp
  891.         mov  cs:[saved_sp],ax
  892.         mov  ax,ss
  893.         mov  cs:[saved_ss],ax
  894.         mov  ax,cs
  895.         mov  ss,ax
  896.         mov  sp,offset cgroup:local_stack_top
  897.         mov  ax,cs:[saved_ax]
  898. endif ; use_local_stack
  899. ;
  900. ; Save registers
  901. ;
  902.         save_regs
  903. ;
  904. ; Make DS:BX point to the request header
  905. ;
  906.         lds  bx,cs:[request_pointer]
  907.         debug_message '{ request header is at '
  908.         debug_hex_word ds
  909.         debug_show_char ':'
  910.         debug_hex_word bx
  911.         debug_message ' data is '
  912.         debug_hex_string ds,[bx],<byte ptr ds:[bx]>,byte
  913.         debug_message <'}',13,10>
  914. ;
  915. ; Get the command code, jump to error handler for bad command,
  916. ; look up the command in the table
  917. ;
  918.         mov  al,ds:[bx].D_REQ_command    ; Get the command code
  919.         cmp  al,CLK_last_command    ; See if it is too large
  920.         jcond jg, bad_command        ; Error if bad code
  921.         cbw                ; Sign extend AL to AX
  922.         mov  si,offset cgroup:CLK_command_table
  923.         add  si,ax
  924.         add  si,ax            ; Now CS:SI points to the entry
  925.                         ;    in the command table
  926. ;
  927. ; Now jump to the appropriate handler subroutine.
  928. ;
  929.         jmp  word ptr cs:[si]        ; jump to the appropriate routine
  930.  
  931. CLK_interrupt endp
  932.  
  933. resident_code    ends
  934.  
  935. ;***********************************************************************
  936.  
  937. ;
  938. ; Routines for various operations required by the CLOCK$ "interrupt"
  939. ; handler.  One of these routines is called with DS:BX pointing to the
  940. ; request header, and with the command code in AL.
  941. ;
  942.  
  943. resident_code    segment
  944.  
  945. ;
  946. ; Request code 4: Input
  947. ;
  948. CLK_input    proc near
  949.         debug_message '{ CLOCK$ input '
  950. ;
  951. ; Make sure our time is correct.
  952. ;
  953.         push bx
  954.         call CLK_fix_our_time     ; Get the new time value
  955.         pop  bx
  956. ;
  957. ; Get the requested transfer length from the request header.
  958. ; This is the size of the callers buffer, so make it smaller
  959. ; if necessary, because we will never return more than CLK_date_time_len
  960. ; bytes.  If count is changed from the requested value, inform the caller.
  961. ;
  962.         mov  cx,word ptr ds:[bx].D_RDWR_length    ; Requested length
  963.         debug_message '( request length '
  964.         debug_hex_word cx
  965.         debug_message ' ) '
  966.         cmp  cx,CLK_date_time_len    ; Check if CX is too large
  967.         jbe  CLK_inp_len_ok         ; Skip ahead if count OK
  968.         mov  cx,CLK_date_time_len    ; Set CX to max size
  969.         mov  word ptr ds:[bx].D_RDWR_length,cx ; Return true length
  970. CLK_inp_len_ok:
  971.         jcond jcxz, exit_ok    ; Exit if there is nothing to do
  972. ;
  973. ; Get the address of the caller's buffer, from the request header.
  974. ; Copy data from the current date and time buffer to the caller's buffer.
  975. ;
  976.         les  di,dword ptr ds:[bx].D_RDWR_buffer ; Destination address
  977.         mov  si,offset cgroup:CLK_date_time ; Make DS:SI point to source
  978.         push cs
  979.         pop  ds
  980.         cld                ; Make sure the move goes forwards
  981.         debug_message '( move from DS:SI = '
  982.         debug_hex_word ds
  983.         debug_show_char ':'
  984.         debug_hex_word si
  985.         debug_message ' to ES:DI = '
  986.         debug_hex_word es
  987.         debug_show_char ':'
  988.         debug_hex_word di
  989.         debug_message ' length '
  990.         debug_hex_word cx
  991.         debug_message ' ) '
  992.         debug_message '{ data: '
  993.         debug_hex_string ds,[si],cx,word
  994.         debug_message '} '
  995.         rep movsb            ; Do the data transfer
  996.         jmp  exit_ok
  997. CLK_input    endp
  998.  
  999. ;
  1000. ; Request code 5: Non destructive read, no wait
  1001. ;
  1002. CLK_peek     proc near
  1003.         debug_message '{ CLOCK$ peek '
  1004. ;
  1005. ; Make sure our time is correct.
  1006. ;
  1007.         push bx
  1008.         call CLK_fix_our_time     ; Get the new time value
  1009.         pop  bx
  1010. ;
  1011. ; Return first byte from the current date buffer
  1012. ;
  1013.         mov  al,byte ptr cs:[CLK_date_time] ; First byte of day
  1014.         mov  ds:[bx].D_PEEK_char,al ; Return the byte
  1015.         jmp  exit_ok            ; Finished successfully
  1016. CLK_peek     endp
  1017.  
  1018. ;
  1019. ; Request code 8: Output
  1020. ; Request code 9: Output with verify
  1021. ;
  1022. CLK_output   proc near
  1023.         debug_message '{ CLOCK$ output '
  1024. ;
  1025. ; Get the requested transfer length
  1026. ;
  1027.         mov  cx,word ptr ds:[bx].D_RDWR_length    ; Length of write
  1028. ;
  1029. ; CX contains the size of the callers buffer.  If it is too small, then
  1030. ; ignore the write request.  If it is too large, perform the write as
  1031. ; usual, but ignore any extra data in the buffer.  In either case, let the
  1032. ; caller believe that the write was successful.
  1033. ;
  1034.         debug_message '( request length '
  1035.         debug_hex_word cx
  1036.         debug_message ' ) '
  1037.         cmp  cx,CLK_date_time_len    ; Check if length is OK
  1038.         jcond jb, bad_length        ; Error if length is too small
  1039.         mov  cx,CLK_date_time_len    ; Set CX to max size
  1040. ;
  1041. ; Get address of caller's data buffer from the request header.
  1042. ; Make sure the caller's data is valid
  1043. ;
  1044.         lds  si,dword ptr ds:[bx].D_RDWR_buffer ; Source data address
  1045.         call CLK_check_data_validity    ; Check for bad date or time
  1046.         jcond jc, general_failure    ; Return error code
  1047. ;
  1048. ; Copy data from the caller's buffer to the current date and time buffer.
  1049. ;
  1050.         push cs             ; Make ES:DI point to destination
  1051.         pop  es             ;
  1052.         mov  di,offset cgroup:CLK_date_time ;
  1053.         cld                ; Make sure the move goes forwards
  1054.         debug_message '( move from DS:SI = '
  1055.         debug_hex_word ds
  1056.         debug_show_char ':'
  1057.         debug_hex_word si
  1058.         debug_message ' to ES:DI = '
  1059.         debug_hex_word es
  1060.         debug_show_char ':'
  1061.         debug_hex_word di
  1062.         debug_message ' length '
  1063.         debug_hex_word cx
  1064.         debug_message ' ) '
  1065.         debug_message '{ data: '
  1066.         debug_hex_string ds,[si],cx,word
  1067.         debug_message '} '
  1068.         rep movsb            ; Do the data transfer
  1069. ;
  1070. ; Make sure other timers get told about the new time.
  1071. ;
  1072.         call CLK_set_other_timers
  1073.         jmp  short exit_ok
  1074. CLK_output   endp
  1075.  
  1076. resident_code    ends
  1077.  
  1078. ;***********************************************************************
  1079.  
  1080. ;
  1081. ; Device "interrupt" handlers jump to one of these labels when they have
  1082. ; finished.  If there is more than one device driver in the same source
  1083. ; file, they can all share these routines.
  1084. ;
  1085.  
  1086. resident_code    segment
  1087.  
  1088. exit_proc    proc far
  1089.  
  1090. ;
  1091. ; Here for successful completion
  1092. ;
  1093. exit_ok:
  1094.         debug_message '(ok) '
  1095.         mov  ax,D_STAT_done shl 8    ; Request completed
  1096. if do_debug_writes
  1097.         jmp  exit_save_status
  1098. else
  1099.         jmp  short exit_save_status
  1100. endif
  1101.  
  1102. ;
  1103. ; Here for bad command
  1104. ;
  1105. bad_command:
  1106.         debug_message '(bad command) '
  1107.         mov  al,D_ERR_bad_command
  1108.         jmp  short exit_error
  1109.  
  1110. ;
  1111. ; Here for bad length
  1112. ;
  1113. bad_length:
  1114.         debug_message '(bad length) '
  1115.         mov  al,D_ERR_bad_length
  1116.         jmp  short exit_error
  1117.  
  1118. ;
  1119. ; Here for general failure
  1120. ;
  1121. general_failure:
  1122.         debug_message '(invalid data) '
  1123.         mov  al,D_ERR_general
  1124.  
  1125. ;
  1126. ; Here to return error code
  1127. ;
  1128. exit_error:
  1129.         mov  ah,D_STAT_error+D_STAT_done ; Completed with error
  1130.  
  1131. ;
  1132. ; Here to save the status code that is already in AH (and error code in AL)
  1133. ;
  1134. exit_save_status:
  1135. ;
  1136. ; Make DS:BX point to the request header, then save the status word
  1137. ;
  1138.         lds  bx,cs:[request_pointer]
  1139.         mov  word ptr [bx].D_REQ_status,ax
  1140. ;
  1141. ; Restore registers and exit
  1142. ;
  1143.         debug_message <'}',13,10>
  1144.         restore_regs
  1145. if use_local_stack
  1146. ;
  1147. ; Switch back to the normal stack.
  1148. ;
  1149.         mov  cs:[saved_ax],ax
  1150.         mov  ax,cs:[saved_ss]
  1151.         mov  ss,ax
  1152.         mov  sp,cs:[saved_sp]
  1153.         mov  ax,cs:[saved_ax]
  1154. endif ; use_local_stack
  1155.         ret                ; FAR return
  1156.  
  1157. exit_proc    endp
  1158.  
  1159. resident_code    ends
  1160.  
  1161. ;***********************************************************************
  1162.  
  1163. ;
  1164. ; Convert dates between various formats.
  1165. ;
  1166.  
  1167. resident_data    segment
  1168.  
  1169. ;
  1170. ; This table contains the cumulative days per month,
  1171. ; not allowing for leap years.
  1172. ;
  1173. CLK_month_length_table label word
  1174.     dw 0, 31, 59, 90, 120, 151, 181     ; 0, Jan, Jan+Feb, ...
  1175.     dw 212, 243, 273, 304, 334, 365     ; ..., Jan+Feb+...+Dec
  1176.  
  1177. resident_data    ends
  1178.  
  1179. resident_code    segment
  1180.  
  1181. ;
  1182. ; Convert from years, months, days
  1183. ; to number of days since 1-Jan-1980
  1184. ;
  1185.  
  1186. CLK_conv_ccyymmdd_days proc near
  1187.     assume ds:cgroup
  1188.     debug_message '{ conv_ccyymmdd_days '
  1189. ;
  1190. ; Find number of years since 1980.  Treat 1980 as year number 0.
  1191. ; This should never be more than 100, if we assume that the
  1192. ; IBM-PC will be obsolete by the year 2079.
  1193. ;
  1194.     mov    al,[CLK_date_century]
  1195.     debug_message '( century '
  1196.     debug_hex_byte al
  1197.     debug_message ' ) '
  1198.     mov    bx,100
  1199.     mul    bl
  1200.     mov    bl,[CLK_date_year]
  1201.     debug_message '( year '
  1202.     debug_hex_byte bl
  1203.     debug_message ' ) '
  1204.     add    ax,bx
  1205.     sub    ax,1980
  1206.     debug_message '( years since 1980 '
  1207.     debug_hex_word ax
  1208.     debug_message ' ) '
  1209.     mov    cx,ax    ; Save number of years for later
  1210. ;
  1211. ; Convert number of years to number of days.
  1212. ;
  1213.     call    CLK_conv_years_days
  1214.     debug_message '( days from years '
  1215.     debug_hex_word ax
  1216.     debug_message ' ) '
  1217. ;
  1218. ; Add the number of days for completed months this year.
  1219. ; This is found from a lookup table.
  1220. ;
  1221.     mov    bl,[CLK_date_month]
  1222.     debug_message '( month '
  1223.     debug_hex_byte bl
  1224.     debug_message ' ) '
  1225.     xor    bh,bh
  1226.     shl    bx,1                ; Each entry is two bytes
  1227.     mov    bx,word ptr ds:[CLK_month_length_table][bx-2]
  1228.     debug_message '( days from months '
  1229.     debug_hex_word bx
  1230.     debug_message ' ) '
  1231.     add    ax,bx
  1232. ;
  1233. ; If it March or later, and if this year is a leap year, add another day.
  1234. ;
  1235.     test    [CLK_date_year],3        ; Is the year a multiple of 4 ?
  1236.     jnz    CLK_conv_ccyymmdd_not_leap
  1237.     cmp    [CLK_date_month],3        ; Is it March or later ?
  1238.     jb    CLK_conv_ccyymmdd_not_leap
  1239.     inc    ax                ; Add another day
  1240.     debug_message '( Add 1 for leap year March or later ) '
  1241. CLK_conv_ccyymmdd_not_leap:
  1242. ;
  1243. ; Add the current day of the month, less 1.
  1244. ;
  1245.     mov    bl,[CLK_date_day]
  1246.     debug_message '( day of month '
  1247.     debug_hex_byte bl
  1248.     debug_message ' ) '
  1249.     dec    bl
  1250.     xor    bh,bh
  1251.     add    ax,bx
  1252.     debug_message '( total days '
  1253.     debug_hex_word ax
  1254.     debug_message ' ) '
  1255. ;
  1256. ; Store the result.
  1257. ;
  1258.     mov    [CLK_date_days],ax
  1259.     debug_message '} '
  1260.     ret
  1261. CLK_conv_ccyymmdd_days endp
  1262.  
  1263. ;
  1264. ; Convert from number of days since 1-Jan-1980
  1265. ; to years, months, days.
  1266. ;
  1267.  
  1268. CLK_conv_days_ccyymmdd proc near
  1269.     debug_message '{ conv_days_ccyymmdd '
  1270. ;
  1271. ; Divide the day number by 365.25 to get number of years.
  1272. ; There is no need to worry about the century years not being leap years,
  1273. ; because this software was not running in 1900; 2000 will be a leap year;
  1274. ; and it is unlikely that this software will be running in the year 2100.
  1275. ;
  1276. ; This division is done by multiplying by 4 and dividing by 1461.  The
  1277. ; intermediate result will be longer than 16 bits, but that poses no problems.
  1278. ;
  1279.     mov    ax,[CLK_date_days]
  1280.     debug_message '( days '
  1281.     debug_hex_word ax
  1282.     debug_message ' ) '
  1283.     mov    bx,4
  1284.     mul    bx
  1285.     mov    bx,1461
  1286.     div    bx
  1287.     debug_message '( divide by 365.25 gives '
  1288.     debug_hex_word ax
  1289.     debug_message ' years since 1980 ) '
  1290. ;
  1291. ; Now the number of years is in AX.  The additional days (remainder)
  1292. ; in DX may be incorrect.
  1293. ;
  1294. ; Add 1980 and convert to year and century values.
  1295. ;
  1296.     mov    cx,ax    ; Save for later
  1297.     add    ax,1980
  1298.     mov    bl,100
  1299.     div    bl
  1300.     mov    [CLK_date_century],al
  1301.     mov    [CLK_date_year],ah
  1302.     debug_message '( century '
  1303.     debug_hex_byte al
  1304.     debug_message ' ) ( year '
  1305.     debug_hex_byte ah
  1306.     debug_message ' ) '
  1307. ;
  1308. ; Find the number of days accounted for by the number of full years.
  1309. ; Subtract that from the total days to get number of days this year.
  1310. ; Add 1 to make the first of January day number 1.
  1311. ;
  1312.     mov    ax,cx
  1313.     call CLK_conv_years_days
  1314.     debug_message '( days from full years '
  1315.     debug_hex_word ax
  1316.     debug_message ' ) '
  1317.     mov    bx,[CLK_date_days]
  1318.     xchg    ax,bx
  1319.     sub    ax,bx
  1320.     inc    ax
  1321.     debug_message '( day within year '
  1322.     debug_hex_word ax
  1323.     debug_message ' ) '
  1324. ;
  1325. ; If it is a leap year and the day-within-year is 60, then it must be 29-Feb.
  1326. ; If a leap year and the day is above 60, subtact one from the day number.
  1327. ; This will allow the month to be found by searching through the cumulative
  1328. ; month length table.
  1329. ;
  1330.     mov    cl,[CLK_date_year]
  1331.     test    cl,3                ; Is it a multiple of 4 ?
  1332.     jnz    CLK_conv_days_not_leap
  1333.     cmp    ax,60                ; Is is 29-Feb ?
  1334.     jb    CLK_conv_days_not_leap        ; Actually, it is a leap year,
  1335.                         ; but it is still Jan or Feb
  1336.     jne    CLK_conv_days_leap
  1337.     debug_message '( leap year 29-Feb ) '
  1338.     mov    [CLK_date_month],2        ; It is 29-Feb
  1339.     mov    [CLK_date_day],29
  1340.     jmp    CLK_conv_days_end
  1341. CLK_conv_days_leap:
  1342.     debug_message '( subtract 1 for leap year March or later ) '
  1343.     dec    ax                ; Subtract a day, because it is
  1344.                         ; March or later in a leap year
  1345. CLK_conv_days_not_leap:
  1346. ;
  1347. ; Find the month by searching through the cumulative month length
  1348. ; table for an entry larger than or equal to the day number in AX.
  1349. ; (AX=1 for 1-Jan.)
  1350. ;
  1351.     mov    bx,2
  1352. CLK_conv_days_month_loop:
  1353.     cmp    ax,word ptr ds:[CLK_month_length_table][bx]
  1354.     jbe    CLK_conv_days_end_month_loop
  1355.     add    bx,2
  1356.     jmp    CLK_conv_days_month_loop
  1357. CLK_conv_days_end_month_loop:
  1358.     mov    cx,bx
  1359.     shr    cx,1
  1360.     debug_message '( month from lookup '
  1361.     debug_hex_word cx
  1362.     debug_message ' ) '
  1363.     mov    [CLK_date_month],cl
  1364. ;
  1365. ; Find the day within the month.
  1366. ;
  1367.     sub    ax,word ptr ds:[CLK_month_length_table][bx-2]
  1368.     mov    [CLK_date_day],al
  1369.     debug_message '( day within month '
  1370.     debug_hex_word ax
  1371.     debug_message ' ) '
  1372. CLK_conv_days_end:
  1373.     debug_message '} '
  1374.     ret
  1375. CLK_conv_days_ccyymmdd endp
  1376.  
  1377. ;
  1378. ; Convert a number of years since 1980 to a number of days.
  1379. ; 1980 is year number 0.
  1380. ; Input years passed in AL, resulting days in AX.
  1381. ;
  1382.  
  1383. CLK_conv_years_days proc near
  1384. ;
  1385. ; Multiply number of years by 365.
  1386. ; This result will be easily representable in 16 bits.
  1387. ;
  1388.     mov    cx,ax                ; Save years for later
  1389.     mov    bx,365
  1390.     mul    bx
  1391. ;
  1392. ; Add one day for every fourth year (leap year).
  1393. ;
  1394. ; Because we are dealing only with the period from 1980 to 2079,
  1395. ; we do not have to adjust the number of leap years.
  1396. ; The year 2000 is a leap year, although 1900, 2100, 2200 and 2300 are not.
  1397. ;
  1398.     add    cx,3    ; CX := (years + 3)/4
  1399.     shr    cx,1    ;
  1400.     shr    cx,1    ;
  1401.     add    ax,cx    ; Add to total days
  1402.     ret
  1403. CLK_conv_years_days endp
  1404.  
  1405. ;
  1406. ; Convert from number of days since 1-Jan-1980
  1407. ; to day of the week.  (Sunday = 0.)
  1408. ;
  1409.  
  1410. CLK_conv_days_weekday proc near
  1411.     debug_message '{ conv_days_weekday '
  1412. ;
  1413. ; The first of Jan 1980 (day number 0) was a Tuesday (weekday number 2).
  1414. ; To convert a day number to a day of the week, we first add 2,
  1415. ; then find the remainder when divided by 7.
  1416. ;
  1417.     mov    ax,[CLK_date_days]    ; Add two
  1418.     debug_message '( days '
  1419.     debug_hex_word ax
  1420.     debug_message ' ) '
  1421.     add    ax,2
  1422.     xor    dx,dx            ; 32-bit divide by 7
  1423.     mov    bx,7
  1424.     div    bx
  1425.     debug_message '( weekday '
  1426.     debug_hex_word dx
  1427.     debug_message ' ) '
  1428.     mov    [CLK_date_weekday],dl
  1429.     debug_message '} '
  1430.     ret
  1431. CLK_conv_days_weekday endp
  1432.  
  1433. resident_code    ends
  1434.  
  1435. ;***********************************************************************
  1436.  
  1437. ;
  1438. ; Binary <--> BCD conversions.
  1439. ;
  1440.  
  1441. resident_code    segment
  1442.  
  1443. ;
  1444. ; Convert binary number in AL to BCD (still in AL).
  1445. ;
  1446.  
  1447. conv_binary_BCD proc    near
  1448. ;
  1449. ; The AAM instruction does most of the work.
  1450. ; It places the high BCD digit in AH and the low BCD digit in AL.
  1451. ;
  1452.     aam
  1453. ;
  1454. ; Shift everything into place.
  1455. ; (The undocumented {AAD 16} instruction would do this in one step.)
  1456. ;
  1457.     push    cx
  1458.     mov    cl,4    ; Shift AH value to high nybble
  1459.     shl    ah,cl
  1460.     or    al,ah    ; Merge the nybbles into AL
  1461.     pop    cx
  1462.     ret
  1463. conv_binary_BCD endp
  1464.  
  1465. ;
  1466. ; Convert BCD number in AL to binary (still in AL).
  1467. ;
  1468.  
  1469. conv_BCD_binary proc    near
  1470. ;
  1471. ; Shift the high BCD digit into AH.
  1472. ; (The undocumented {AAM 16} would do this in one step.)
  1473. ;
  1474.     push    cx
  1475.     xor    ah,ah    ; Shift high nybble to AH
  1476.     mov    cl,4
  1477.     shl    ax,cl
  1478.     shr    al,cl    ; Low nybble back into position
  1479.     pop    cx
  1480. ;
  1481. ; The AAD instruction does the rest of the work.
  1482. ; It gets the high BCD digit from AH and the low BCD digit from AL,
  1483. ; and places the binary result into AL.
  1484. ;
  1485.     aad
  1486.     ret
  1487. conv_BCD_binary endp
  1488.  
  1489. resident_code    ends
  1490.  
  1491. ;
  1492. ; Check that the value in AL is a valid BCD number.
  1493. ; Destroys AH in the process, but returns with AL unmodified.
  1494. ; Returns with carry flag set if not a valid BCD number.
  1495. ;
  1496. startup_code    segment
  1497.  
  1498. CLK_check_BCD    proc    near
  1499.     mov    ah,al            ; Keep AL unchanged
  1500.     and    ah,0Fh            ; Check low nybble
  1501.     cmp    ah,0Ah            ; Must be 9 or less
  1502.     jae    CLK_check_BCD_end    ; Carry flag will be off if
  1503.                     ;   jump is taken
  1504.     mov    ah,al            ; Check high nybble
  1505.     and    ah,0F0h         ;
  1506.     cmp    ah,0A0h         ; Must be 90h or less
  1507.                     ; Carry flag will be on if OK
  1508. CLK_check_BCD_end:
  1509. ;
  1510. ; Now complement the carry flag and return.
  1511. ; The carry flag will then be set if either test failed.
  1512. ;
  1513.     cmc
  1514.     ret
  1515. CLK_check_BCD    endp
  1516.  
  1517. startup_code    ends
  1518.  
  1519. ;***********************************************************************
  1520.  
  1521. ;
  1522. ; Some subroutines to read and set timers external to this device driver.
  1523. ; This includes the BIOS timer, the AT BIOS real time clock, and battery
  1524. ; backed-up timers on peripheral cards.
  1525. ; The routines here simply call other routines, choosing which others
  1526. ; to call according to the setup options.
  1527. ;
  1528.  
  1529. resident_code    segment
  1530.  
  1531. ;
  1532. ; Make sure that the time is correct.  Do this by checking other time
  1533. ; references.
  1534. ;
  1535. CLK_fix_our_time proc near
  1536.         debug_message '{ fix our time '
  1537. ;
  1538. ; Ensure that DS points to the correct segment
  1539. ;
  1540.         push ds             ; Save old DS
  1541.         push cs             ; Make DS point to code segment
  1542.         pop  ds             ;
  1543. ;
  1544. ; The initialisation routine should have stored pointers to a suitable
  1545. ; procedure in the CLK_get_procedure_ptr word.  Now call the procedure.
  1546. ;
  1547.         call word ptr ds:[CLK_get_procedure_ptr]
  1548. ;
  1549. ; Restore DS segment register and return
  1550. ;
  1551.         pop  ds
  1552.         debug_message '} '
  1553.         ret
  1554. CLK_fix_our_time endp
  1555.  
  1556. ;
  1557. ; Make sure that the time used by other timers in the system is correct.
  1558. ; One reason for doing this is so that the "other timers" can tell us
  1559. ; the correct time next time we need to ask them.
  1560. ;
  1561. CLK_set_other_timers proc near
  1562.         debug_message '{ set others '
  1563. ;
  1564. ; Ensure that DS points to the correct segment
  1565. ;
  1566.         push ds             ; Save old DS
  1567.         push cs             ; Make DS point to code segment
  1568.         pop  ds             ;
  1569. ;
  1570. ; The initialisation routine should have stored a pointer to a suitable
  1571. ; procedure in the CLK_set_procedures array.  Now call the procedure.
  1572. ;
  1573.         call word ptr ds:[CLK_set_procedure_ptr]
  1574. ;
  1575. ; Also set the plain BIOS idea of the time.
  1576. ; (CLK_set_procedure_ptr will point to a null procedure if we are
  1577. ; using the plain BIOS without any other clock.)
  1578. ;
  1579.         call CLK_set_BIOS_time
  1580. ;
  1581. ; Restore DS segment register and return
  1582. ;
  1583.         pop  ds
  1584.         debug_message '} '
  1585.         ret
  1586. CLK_set_other_timers endp
  1587.  
  1588. resident_code    ends
  1589.  
  1590. ;***********************************************************************
  1591.  
  1592. ;
  1593. ; Check the validity of the date and time passed in a write request.
  1594. ; On entry, DS:SI points to the caller's data.
  1595. ; On exit, carry flag will be set if the data is invalid.
  1596. ;
  1597. resident_code    segment
  1598.  
  1599. CLK_check_data_validity proc near
  1600.         debug_message '{ validity check '
  1601.         debug_message ' ( data: '
  1602.         debug_hex_string ds,[si],6,word
  1603.         debug_message ') '
  1604. ;
  1605. ; Check hours, minutes, seconds and hundredths of secs.
  1606. ; Note funny order in which things are stored.
  1607. ;
  1608.         cmp  byte ptr ds:[si+3], 24    ; Hours must be < 24
  1609.         jnc  short CLK_check_end
  1610.         cmp  byte ptr ds:[si+2], 60    ; Minutes must be < 60
  1611.         jnc  short CLK_check_end
  1612.         cmp  byte ptr ds:[si+5], 60    ; Seconds must be < 60
  1613.         jnc  short CLK_check_end
  1614.         cmp  byte ptr ds:[si+4], 60    ; Hundredths must be < 100
  1615.         cmp  al, 100
  1616. CLK_check_end:
  1617. ;
  1618. ; Toggle the carry flag, so this routine returns with carry set if
  1619. ; there was a problem, and clear if there was no problem.
  1620. ;
  1621.         cmc
  1622.         debug_message '} '
  1623.         ret
  1624. CLK_check_data_validity endp
  1625.  
  1626. resident_code    ends
  1627.  
  1628. ;***********************************************************************
  1629.  
  1630. ;
  1631. ; This procedure will read the normal BIOS timer, and check whether either
  1632. ; the date has changed or the time has changed by more than a few minutes
  1633. ; since the last call.  If necessary, it then reads some other clock (which
  1634. ; has coarser granularity but higher accuracy), and re-calibrates the
  1635. ; normal BIOS timer.
  1636. ;
  1637.  
  1638. resident_code    segment
  1639.  
  1640. CLK_get_BIOS_or_other_time proc near
  1641. ;
  1642. ; Ask BIOS for the new date and time
  1643. ;
  1644.     call    CLK_get_BIOS_time
  1645. ;
  1646. ; See if the date has changed
  1647. ;
  1648.     mov    ax,[CLK_date_days]    ; Current date
  1649.     cmp    ax,[CLK_last_date]    ; Previous date
  1650.     jne    CLK_must_get_other_time
  1651. ;
  1652. ; See if the time has changed by more than five minutes since
  1653. ; last time we got the time from the AT BIOS battery-backed clock.
  1654. ;
  1655.     mov    al,[CLK_time_hour]    ; Current hour*60 + minute
  1656.     mov    ah,60
  1657.     mul    ah
  1658.     add    al,[CLK_time_min]
  1659.     adc    ah,0
  1660.     sub    ax,[CLK_last_time]    ; Subtract previous time
  1661.     cmp    ax,5            ; Did time go forwards < 5 mins ?
  1662.     jng    CLK_dont_get_other_time
  1663. CLK_must_get_other_time:
  1664. ;
  1665. ; Read the other clock and set the normal BIOS timer to the current time.
  1666. ;
  1667.     call    word ptr ds:[CLK_get_other_procedure_ptr]
  1668.     call    CLK_set_BIOS_time
  1669. CLK_dont_get_other_time:
  1670. ;
  1671. ; Remember the current date and time.  This will be the "old" date and time
  1672. ; next time the clock is read.
  1673. ;
  1674.     mov    ax,[CLK_date_days]    ; Date
  1675.     mov    [CLK_last_date],ax
  1676.     mov    al,[CLK_time_hour]    ; Hours multiplied by 60
  1677.     mov    ah,60
  1678.     mul    ah
  1679.     add    al,[CLK_time_min]    ; Plus minutes
  1680.     adc    ah,0
  1681.     mov    [CLK_last_time],ax
  1682.     ret
  1683. CLK_get_BIOS_or_other_time endp
  1684.  
  1685. resident_code    ends
  1686.  
  1687. ;***********************************************************************
  1688.  
  1689. ;
  1690. ; Handle a AT-type computer, with clock chip support in the BIOS.
  1691. ;
  1692. ; Note that some of these routines are in the startup_code and some
  1693. ; are in the resident_code.
  1694. ;
  1695.  
  1696. startup_code    segment
  1697.  
  1698. ;
  1699. ; Check for AT-type BIOS support of the clock chip.
  1700. ;
  1701. ; BIOS interrupt 1A function 4 should get the date from the real time clock
  1702. ; if the computer has an AT-type BIOS.  The call should return invalid data if
  1703. ; the BIOS does not support that function.
  1704. ;
  1705. ; Return with CARRY set if no AT BIOS support for clock chip.
  1706. ;
  1707.  
  1708. CLK_find_AT_BIOS proc near
  1709.         assume ds:cgroup
  1710.     debug_message '{ find AT BIOS '
  1711. ;
  1712. ; Do an interrupt 1A function 4 call.  (Get date from real time clock
  1713. ; on an AT machine.)
  1714. ;
  1715.     xor    cx,cx            ; This will help find errors
  1716.     xor    dx,dx            ;
  1717.     mov    ah,04h            ; Ask BIOS for the date
  1718.     int    1Ah            ;
  1719. ;
  1720. ; Check that the results are reasonable.  If not, then it cannot
  1721. ; have an AT-compatible BIOS.
  1722. ; The results are all in BCD.
  1723. ;
  1724.     cmp    ch,19h            ; Is the century reasonable ?
  1725.     jb    CLK_find_no_AT        ; no
  1726.     cmp    cl,99h            ; Is the year reasonable ?
  1727.     ja    CLK_find_no_AT        ; no
  1728.     cmp    dh,12h            ; Is the month reasonable ?
  1729.     ja    CLK_find_no_AT        ; no
  1730.     cmp    dl,31h            ; Is the day reasonable ?
  1731.     ja    CLK_find_no_AT        ; no
  1732. ;
  1733. ; Return with no carry if all is correct
  1734. ;
  1735.     clc
  1736.     debug_message '(ok) } '
  1737.         ret
  1738. ;
  1739. ; If there is no AT BIOS clock support, return with carry flag set.
  1740. ;
  1741. CLK_find_no_AT:
  1742.         stc
  1743.     debug_message '(failed) } '
  1744.         ret
  1745. CLK_find_AT_BIOS endp
  1746.  
  1747. startup_code    ends
  1748.  
  1749. resident_code    segment
  1750.  
  1751. ;
  1752. ; Get the time from AT BIOS.
  1753. ;
  1754.  
  1755. CLK_get_AT_BIOS_time proc near
  1756.         assume ds:cgroup
  1757.     debug_message '{ get AT BIOS '
  1758. ;
  1759. ; BIOS interrupt 1Ah functions 2 and 4 get time and date from AT BIOS
  1760. ;
  1761.     mov    ah,02h            ; Get time
  1762.     int    1Ah            ;
  1763.     mov    al,ch            ; Save it
  1764.     call    conv_BCD_binary     ;
  1765.     mov    [CLK_time_hour],al    ;
  1766.     mov    al,cl            ;
  1767.     call    conv_BCD_binary     ;
  1768.     mov    [CLK_time_min],al    ;
  1769.     mov    al,dh            ;
  1770.     call    conv_BCD_binary     ;
  1771.     mov    [CLK_time_sec],al    ;
  1772.     mov    [CLK_time_hsec],0    ; There are no hundredths ???
  1773.     mov    ah,04h            ; Get date
  1774.     int    1Ah            ;
  1775.     mov    al,ch            ; Save it
  1776.     call    conv_BCD_binary     ;
  1777.     mov    [CLK_date_century],al    ;
  1778.     mov    al,cl            ;
  1779.     call    conv_BCD_binary     ;
  1780.     mov    [CLK_date_year],al    ;
  1781.     mov    al,dh            ;
  1782.     call    conv_BCD_binary     ;
  1783.     mov    [CLK_date_month],al    ;
  1784.     mov    al,dl            ;
  1785.     call    conv_BCD_binary     ;
  1786.     mov    [CLK_date_day],al    ;
  1787. ;
  1788. ; The date is in century, year, month, day form
  1789. ; so it must be converted to a number of days since 1-Jan-1980.
  1790. ;
  1791.     call    CLK_conv_ccyymmdd_days
  1792.     ret
  1793.     debug_message '} '
  1794. CLK_get_AT_BIOS_time endp
  1795.  
  1796. ;
  1797. ; Tell the AT BIOS to set the time.
  1798. ;
  1799.  
  1800. CLK_set_AT_BIOS_time proc near
  1801.         assume ds:cgroup
  1802.     debug_message '{ set AT BIOS '
  1803. ;
  1804. ; Convert the date from days since 1-Jan-1980 to year, month, day
  1805. ;
  1806.     call    CLK_conv_days_ccyymmdd
  1807. ;
  1808. ; Set the AT BIOS clock.
  1809. ; Everything must be in BCD.
  1810. ;
  1811.     mov    al,[CLK_time_hour]
  1812.     call    conv_binary_BCD
  1813.     mov    ch,al
  1814.     mov    al,[CLK_time_min]
  1815.     call    conv_binary_BCD
  1816.     mov    cl,al
  1817.     mov    al,[CLK_time_sec]
  1818.     call    conv_binary_BCD
  1819.     mov    dh,al
  1820.     xor    dl,dl                ; Not daylight saving time
  1821.     mov    ah,3
  1822.     int    1Ah
  1823.     mov    al,[CLK_date_century]
  1824.     call    conv_binary_BCD
  1825.     mov    ch,al
  1826.     mov    al,[CLK_date_year]
  1827.     call    conv_binary_BCD
  1828.     mov    cl,al
  1829.     mov    al,[CLK_date_month]
  1830.     call    conv_binary_BCD
  1831.     mov    dh,al
  1832.     mov    al,[CLK_date_day]
  1833.     call    conv_binary_BCD
  1834.     mov    dl,al
  1835.     mov    ah,5
  1836.     int    1Ah
  1837.     debug_message '} '
  1838.     ret
  1839. CLK_set_AT_BIOS_time endp
  1840.  
  1841. resident_code    ends
  1842.  
  1843. ;***********************************************************************
  1844.  
  1845. ;
  1846. ; Handle a National Semiconductor MM58167AN clock chip.
  1847. ; Most add-on clock cards use this chip.
  1848. ;
  1849. ; There are at least three different conventions for using the chip
  1850. ; RAM registers to remember the date.  We use the method used by the
  1851. ; TIMER program provided with some add-on clock cards; this is not
  1852. ; the same as the method used by the SETCLOCK and GETCLOCK programs
  1853. ; provided with some other add-on clock cards, and also differs from
  1854. ; the usage suggested in the chip documentation.
  1855. ;
  1856. ; Note that some of these routines are in the startup_code and some
  1857. ; are in the resident_code.
  1858. ;
  1859. ; There is a word in the resident_data segment to say whether or
  1860. ; not to use these routines, and what base address to use for the
  1861. ; clock chip.
  1862. ;
  1863.  
  1864. startup_code    segment
  1865.  
  1866. ;
  1867. ; Look for the MM58167AN clock chip.
  1868. ;
  1869. ; The chip is usually addressed from I/O port 00C0, 0240, 02C0 or 0340,
  1870. ; so we look in all these places, unless the user specified a location,
  1871. ; in which case look there only.  The user can also explicitly say don't
  1872. ; use the clock chip.
  1873. ;
  1874. ; On entry, CLK_chip_IO_base is zero if chip will not be used, or
  1875. ; 0FFFFh to search for chip in standard places, or some other value
  1876. ; representing the chip's actual IO base address.
  1877. ;
  1878. ; Return with CARRY set if no clock chip.
  1879. ; CLK_chip_IO_base will be set to the start address if there is a chip,
  1880. ; or to zero if there is none.
  1881. ;
  1882.  
  1883. CLK_find_MM58167AN proc near
  1884.     assume ds:cgroup
  1885.     debug_message '{ find MM58167AN chip '
  1886. ;
  1887. ; Check whether the user told us not to use the clock chip
  1888. ;
  1889.     mov  ax,[CLK_chip_IO_base]    ; What the user said
  1890.     or   ax, ax            ; If zero then don't use clock
  1891.     je   CLK_find_no_MM58167AN
  1892. ;
  1893. ; Check whether user said where the chip resides in the IO map
  1894. ;
  1895.     cmp  ax,0FFFFh            ; If not FFFF, look there only
  1896.     jne  CLK_find_MM58167AN_last_resort
  1897. ;
  1898. ; Look in the standard places
  1899. ;
  1900.         mov  word ptr [CLK_chip_IO_base],0240h ; Base address
  1901.         call CLK_check_MM58167AN
  1902.         jnc  CLK_find_MM58167AN_exit           ; Exit if found
  1903.         mov  word ptr [CLK_chip_IO_base],02C0h ; Base address
  1904.         call CLK_check_MM58167AN
  1905.         jnc  CLK_find_MM58167AN_exit           ; Exit if found
  1906.         mov  word ptr [CLK_chip_IO_base],0340h ; Base address
  1907.         call CLK_check_MM58167AN
  1908.         jnc  CLK_find_MM58167AN_exit           ; Exit if found
  1909.         mov  word ptr [CLK_chip_IO_base],00C0h ; Base address
  1910. CLK_find_MM58167AN_last_resort:
  1911.         call CLK_check_MM58167AN     ; See if there is a clock chip
  1912.         jc   CLK_find_no_MM58167AN   ; Error if not found
  1913. CLK_find_MM58167AN_exit:
  1914.     debug_message '( ok '
  1915.     debug_hex_word <word ptr [CLK_chip_IO_base]>
  1916.     debug_message ' ) } '
  1917.     ret
  1918. ;
  1919. ; If there is no clock chip, set the carry flag and zero the base address.
  1920. ;
  1921. CLK_find_no_MM58167AN:
  1922.     mov  word ptr [CLK_chip_IO_base],0
  1923.     debug_message '( failed ) } '
  1924.     stc
  1925.     ret
  1926. CLK_find_MM58167AN endp
  1927.  
  1928. ;
  1929. ; Check if there is an MM58167AN clock chip at the IO base address
  1930. ; specified in CLK_chip_IO_base.  Return with CARRY if there is no chip.
  1931. ;
  1932.  
  1933. CLK_check_MM58167AN proc near
  1934.         assume ds:cgroup
  1935.     debug_message '{ check MM58167AN ( base '
  1936.     debug_hex_word <word ptr [CLK_chip_IO_base]>
  1937.     debug_message ' ) '
  1938. ;
  1939. ; Check the first seven registers, to ensure that they
  1940. ; contain BCD data within the correct range.
  1941. ;
  1942.     mov    dx,[CLK_chip_IO_base]    ; Get chip base address
  1943.     in    al,dx            ; 00: 1/10000 seconds
  1944.     call    CLK_check_BCD
  1945.     jc    CLK_check_no_MM58167AN
  1946.     inc    dx            ; 01: 1/10 and 1/100 seconds
  1947.     in    al,dx
  1948.     call    CLK_check_BCD
  1949.     jc    CLK_check_no_MM58167AN
  1950.     inc    dx            ; 02: seconds
  1951.     in    al,dx
  1952.     call    CLK_check_BCD
  1953.     jc    CLK_check_no_MM58167AN
  1954.     cmp    al,59h            ; Must be less than 60
  1955.     ja    CLK_check_no_MM58167AN
  1956.     inc    dx            ; 03: minutes
  1957.     in    al,dx
  1958.     call    CLK_check_BCD
  1959.     jc    CLK_check_no_MM58167AN
  1960.     cmp    al,59h            ; Must be less than 60
  1961.     ja    CLK_check_no_MM58167AN
  1962.     inc    dx            ; 04: hours
  1963.     in    al,dx
  1964.     call    CLK_check_BCD
  1965.     jc    CLK_check_no_MM58167AN
  1966.     cmp    al,23h            ; Must be less than 24
  1967.     ja    CLK_check_no_MM58167AN
  1968.     inc    dx            ; 05: day of week
  1969.     in    al,dx
  1970.     test    al,al            ; Must be between 1 and 7
  1971.     jz    CLK_check_no_MM58167AN
  1972.     cmp    al,07h
  1973.     ja    CLK_check_no_MM58167AN
  1974.     inc    dx            ; 06: day of month
  1975.     in    al,dx
  1976.     test    al,al            ; Must be between 1 and 31
  1977.     jz    CLK_check_no_MM58167AN
  1978.     cmp    al,31h
  1979.     ja    CLK_check_no_MM58167AN
  1980.     inc    dx            ; 07: month
  1981.     in    al,dx
  1982.     test    al,al            ; Must be between 1 and 12
  1983.     jz    CLK_check_no_MM58167AN
  1984.     cmp    al,12h
  1985.     ja    CLK_check_no_MM58167AN
  1986. ;
  1987. ; It certainly looks like an MM58167AN clock chip.
  1988. ; Return with carry flag clear.
  1989. ;
  1990.     clc
  1991.     debug_message '(ok) } '
  1992.     ret
  1993. CLK_check_no_MM58167AN:
  1994. ;
  1995. ; It is definitely not an MM58167AN clock chip.
  1996. ; Return with carry flag set.
  1997. ;
  1998.     stc
  1999.     debug_message '( failed ) } '
  2000.     ret
  2001. CLK_check_MM58167AN endp
  2002.  
  2003. startup_code    ends
  2004.  
  2005. resident_code    segment
  2006.  
  2007. ;
  2008. ; Get the time from clock chip
  2009. ;
  2010.  
  2011. CLK_get_MM58167AN_time proc near
  2012.     assume ds:cgroup
  2013.     debug_message '{ get MM58167AN '
  2014. ;
  2015. ; Read all the relevant values from the chip's registers
  2016. ;
  2017. ; The "last month" value used by the TIMER program supplied with
  2018. ; some clock cards actually stores the year in register 09 and the
  2019. ; last month in register 08.  We will do the same, for compatibility.
  2020. ; The last month value is   OR( SHL( MAX(month,7),4 ), 80h).
  2021. ;
  2022.     mov    dx,[CLK_chip_IO_base]    ; Address of the first register
  2023.     inc    dx            ; 00: ten thousandths of seconds
  2024.                     ;      (ignore)
  2025.     in    al,dx            ; 01: hundredths of seconds
  2026.     debug_message '( centi '
  2027.     debug_hex_byte al
  2028.     debug_message ' BCD ) '
  2029.     call    conv_BCD_binary     ;     convert from BCD
  2030.     mov    [CLK_time_hsec],al    ;     save
  2031.     inc    dx            ; 02: seconds
  2032.     in    al,dx
  2033.     debug_message '( secs '
  2034.     debug_hex_byte al
  2035.     debug_message ' BCD ) '
  2036.     call    conv_BCD_binary
  2037.     mov    [CLK_time_sec],al
  2038.     inc    dx            ; 03: minutes
  2039.     in    al,dx
  2040.     debug_message '( mins '
  2041.     debug_hex_byte al
  2042.     debug_message ' BCD ) '
  2043.     call    conv_BCD_binary
  2044.     mov    [CLK_time_min],al
  2045.     inc    dx            ; 04: hours
  2046.     in    al,dx
  2047.     debug_message '( hours '
  2048.     debug_hex_byte al
  2049.     debug_message ' BCD ) '
  2050.     call    conv_BCD_binary
  2051.     mov    [CLK_time_hour],al
  2052.     inc    dx            ; 05: day of week (ignore)
  2053.     inc    dx            ; 06: day of month
  2054.     in    al,dx
  2055.     debug_message '( day of month '
  2056.     debug_hex_byte al
  2057.     debug_message ' BCD ) '
  2058.     call    conv_BCD_binary
  2059.     mov    [CLK_date_day],al
  2060.     inc    dx            ; 07: month
  2061.     in    al,dx
  2062.     debug_message '( month '
  2063.     debug_hex_byte al
  2064.     debug_message ' BCD ) '
  2065.     mov    bl,al            ; (save BCD month value)
  2066.     call    conv_BCD_binary
  2067.     mov    [CLK_date_month],al
  2068.     inc    dx            ; 08: RAM : last month
  2069.     in    al,dx
  2070.     debug_message '( last month '
  2071.     debug_hex_byte al
  2072.     debug_message ' ) '
  2073.     mov    bh,al            ; (save last month value)
  2074.     mov    al,bl            ; find MAX(7,current month)
  2075.     cmp    al,7
  2076.     jng    CLK_get_MM58167AN_month_7
  2077.     mov    al,7
  2078. CLK_get_MM58167AN_month_7:
  2079.     mov    cl,4            ; Shift to high nybble
  2080.     shl    al,cl
  2081.     or    al,80h            ; Set high bit
  2082.     debug_message '( new last month value '
  2083.     debug_hex_byte al
  2084.     debug_message ' ) '
  2085.     mov    bl,al            ; Save new last month
  2086.     out    dx,al            ; Store new last month
  2087.     inc    dx            ; 09: year
  2088.     in    al,dx
  2089.     debug_message '( year '
  2090.     debug_hex_byte al
  2091.     debug_message ' BCD ) '
  2092. ;
  2093. ; See if the year has changed.
  2094. ;
  2095.     cmp    bl,bh            ; Is current month smaller than
  2096.                     ; previous month ?
  2097.     jae    CLK_get_MM58167AN_same_year
  2098.     debug_message '( year has changed ) '
  2099.     inc    al            ; Increment year
  2100.     daa
  2101.     out    dx,al            ; Store new year into chip
  2102. CLK_get_MM58167AN_same_year:
  2103.     call    conv_BCD_binary
  2104.     mov    [CLK_date_year],al
  2105. ;
  2106. ; Choose a suitable century
  2107. ;
  2108.     mov    ah,19            ; Assume the year is 19xx.
  2109.     cmp    al,80            ; If last two digits less than 80
  2110.                     ; then the year is 20xx
  2111.     jnb    CLK_get_MM58167AN_century
  2112.     inc    ah
  2113. CLK_get_MM58167AN_century:
  2114.     debug_message '( century '
  2115.     debug_hex_byte ah
  2116.     debug_message' ) '
  2117.     mov    [CLK_date_century],ah
  2118. ;
  2119. ; Convert the date to number of days since 1-Jan-1980
  2120. ;
  2121.     call CLK_conv_ccyymmdd_days
  2122. ;
  2123. ; Finished getting time from chip
  2124. ;
  2125.     ret
  2126.     debug_message '} '
  2127. CLK_get_MM58167AN_time endp
  2128.  
  2129. ;
  2130. ; Set the time on the clock chip.
  2131. ;
  2132.  
  2133. CLK_set_MM58167AN_time proc near
  2134.         assume ds:cgroup
  2135.     debug_message '{ set MM58167AN '
  2136. ;
  2137. ; Convert date to correct format
  2138. ;
  2139.     call CLK_conv_days_ccyymmdd    ; Year, month, day from day number
  2140.     call CLK_conv_days_weekday    ; Find current day of week
  2141. ;
  2142. ; Store data into clock chip
  2143. ;
  2144. ; The "last month" value used by the TIMER program supplied with
  2145. ; some clock cards actually stores the year in register 09 and the
  2146. ; last month in register 08.  We will do the same, for compatibility.
  2147. ; The last month value is   OR( SHL( MAX(month,7),4 ), 80h).
  2148. ;
  2149.     mov    dx,[CLK_chip_IO_base]    ; Base address of clock chip
  2150.     xor    al,al            ; 00: 1/10000 secs (zero)
  2151.     out    dx,al
  2152.     debug_message '( zero 1/10000 sec ) '
  2153.     inc    dx            ; 01: 1/100 secs
  2154.     mov    al,[CLK_time_hsec]
  2155.     call    conv_binary_BCD
  2156.     debug_message '( centi '
  2157.     debug_hex_byte al
  2158.     debug_message ' BCD ) '
  2159.     out    dx,al
  2160.     inc    dx            ; 02: seconds
  2161.     mov    al,[CLK_time_sec]
  2162.     call    conv_binary_BCD
  2163.     debug_message '( secs '
  2164.     debug_hex_byte al
  2165.     debug_message ' BCD ) '
  2166.     out    dx,al
  2167.     inc    dx            ; 03: minutes
  2168.     mov    al,[CLK_time_min]
  2169.     call    conv_binary_BCD
  2170.     debug_message '( mins '
  2171.     debug_hex_byte al
  2172.     debug_message ' BCD ) '
  2173.     out    dx,al
  2174.     inc    dx            ; 04: hours
  2175.     mov    al,[CLK_time_hour]
  2176.     call    conv_binary_BCD
  2177.     debug_message '( hour '
  2178.     debug_hex_byte al
  2179.     debug_message ' BCD ) '
  2180.     out    dx,al
  2181.     inc    dx            ; 05: day of week
  2182.     mov    al,[CLK_date_weekday]
  2183.     inc    al            ; Chip uses Sunday=1, not =0
  2184.     debug_message '( weekday '
  2185.     debug_hex_byte al
  2186.     debug_message ' ) '
  2187.     out    dx,al
  2188.     inc    dx            ; 06: day of month
  2189.     mov    al,[CLK_date_day]
  2190.     call    conv_binary_BCD
  2191.     debug_message '( day of month '
  2192.     debug_hex_byte al
  2193.     debug_message ' BCD ) '
  2194.     out    dx,al
  2195.     inc    dx            ; 07: month
  2196.     mov    al,[CLK_date_month]
  2197.     mov    bl,al
  2198.     call    conv_binary_BCD
  2199.     debug_message '( month '
  2200.     debug_hex_byte al
  2201.     debug_message ' BCD ) '
  2202.     out    dx,al
  2203.     inc    dx            ; 08: RAM : last month
  2204.     mov    al,bl            ; Get current month
  2205.     cmp    al,7            ; find MAX(7,current month)
  2206.     jng    CLK_set_MM58167AN_month_7
  2207.     mov    al,7
  2208. CLK_set_MM58167AN_month_7:
  2209.     mov    cl,4            ; Shift to high nybble
  2210.     shl    al,cl
  2211.     or    al,80h            ; Set high bit
  2212.     debug_message '( new last month value '
  2213.     debug_hex_byte al
  2214.     debug_message ' ) '
  2215.     out    dx,al            ; Store new last month
  2216.     inc    dx            ; 09: RAM : year
  2217.     mov    al,[CLK_date_year]
  2218.     call    conv_binary_BCD
  2219.     debug_message '( year '
  2220.     debug_hex_byte al
  2221.     debug_message ' BCD ) '
  2222.     out    dx,al
  2223. ;
  2224. ; Finished
  2225. ;
  2226.     ret
  2227.     debug_message '} '
  2228. CLK_set_MM58167AN_time endp
  2229.  
  2230. resident_code    ends
  2231.  
  2232. ;***********************************************************************
  2233.  
  2234. ;
  2235. ; Handle an MSM6242 clock chip.
  2236. ;
  2237. ; Note that some of these routines are in the startup_code and some
  2238. ; are in the resident_code.
  2239. ;
  2240. ; There is a word in the resident_data segment to say whether or
  2241. ; not to use these routines, and what base address to use for the
  2242. ; clock chip.
  2243. ;
  2244.  
  2245. startup_code    segment
  2246.  
  2247. ;
  2248. ; Look for the MSM6242 clock chip.
  2249. ;
  2250. ; The chip is usually addressed from I/O port 02C0,
  2251. ; so we look there, unless the user specified a different location,
  2252. ; in which case look there only.  The user can also explicitly say don't
  2253. ; use the clock chip.
  2254. ;
  2255. ; On entry, CLK_chip_IO_base is zero if chip will not be used, or
  2256. ; 0FFFFh to search for chip in standard places, or some other value
  2257. ; representing the chip's actual IO base address.
  2258. ;
  2259. ; Return with CARRY set if no clock chip.
  2260. ; CLK_chip_IO_base will be set to the start address if there is a chip,
  2261. ; or to zero if there is none.
  2262. ;
  2263.  
  2264. CLK_find_MSM6242 proc near
  2265.     assume ds:cgroup
  2266.     debug_message '{ find MSM6242 chip '
  2267. ;
  2268. ; Check whether the user told us not to use the clock chip
  2269. ;
  2270.     mov  ax,[CLK_chip_IO_base]    ; What the user said
  2271.     or   ax, ax            ; If zero then don't use clock
  2272.     je   CLK_find_no_MSM6242
  2273. ;
  2274. ; Check whether user said where the chip resides in the IO map
  2275. ;
  2276.         cmp  ax,0FFFFh            ; If not FFFF, look there only
  2277.         jne  CLK_find_MSM6242_last_resort
  2278. ;
  2279. ; Look in the standard places
  2280. ;
  2281.         mov  word ptr [CLK_chip_IO_base],02C0h ; Base address
  2282. CLK_find_MSM6242_last_resort:
  2283.         call CLK_check_MSM6242       ; See if there is a clock chip
  2284.         jc   CLK_find_no_MSM6242   ; Error if not found
  2285. CLK_find_MSM6242_exit:
  2286.     debug_message '( ok '
  2287.     debug_hex_word <word ptr [CLK_chip_IO_base]>
  2288.     debug_message ' ) } '
  2289.         ret
  2290. ;
  2291. ; If there is no clock chip, set the carry flag and zero the base address.
  2292. ;
  2293. CLK_find_no_MSM6242:
  2294.         mov  word ptr [CLK_chip_IO_base],0
  2295.     debug_message '( failed ) } '
  2296.         stc
  2297.         ret
  2298. CLK_find_MSM6242 endp
  2299.  
  2300. ;
  2301. ; Check if there is an MSM6242 clock chip at the IO base address
  2302. ; specified in CLK_chip_IO_base.  Return with CARRY if there is no chip.
  2303. ;
  2304.  
  2305. CLK_check_MSM6242 proc near
  2306.     assume ds:cgroup
  2307.     debug_message '{ check MSM6242 ( base '
  2308.     debug_hex_word <word ptr [CLK_chip_IO_base]>
  2309.     debug_message ' ) '
  2310. ;
  2311. ; Check the first twelve registers, to ensure that they
  2312. ; contain data within the correct range.  The high 4 bits
  2313. ; should be ignored.
  2314. ;
  2315.     mov    dx,[CLK_chip_IO_base]    ; Get chip base address
  2316.     in    al,dx            ; 00: 1's of seconds
  2317.     and    al, 0Fh
  2318.     cmp    al, 10
  2319.     ja    CLK_check_no_MSM6242
  2320.     inc    dx            ; 01: 10's of seconds
  2321.     in    al,dx
  2322.     and    al, 0Fh
  2323.     cmp    al, 10
  2324.     ja    CLK_check_no_MSM6242
  2325.     inc    dx            ; 02: 1's of minutes
  2326.     in    al,dx
  2327.     and    al, 0Fh
  2328.     cmp    al, 10
  2329.     ja    CLK_check_no_MSM6242
  2330.     inc    dx            ; 03: 10's of minutes
  2331.     in    al,dx
  2332.     and    al, 0Fh
  2333.     cmp    al, 10
  2334.     ja    CLK_check_no_MSM6242
  2335.     inc    dx            ; 04: 1's of hours
  2336.     in    al,dx
  2337.     and    al, 0Fh
  2338.     cmp    al, 10
  2339.     ja    CLK_check_no_MSM6242
  2340.     inc    dx            ; 05: 10's of hours
  2341.     in    al,dx
  2342.     and    al, 0Fh
  2343.     cmp    al, 2
  2344.     ja    CLK_check_no_MSM6242
  2345.     inc    dx            ; 06: 1's of day of month
  2346.     in    al,dx
  2347.     and    al, 0Fh
  2348.     cmp    al,10
  2349.     ja    CLK_check_no_MSM6242
  2350.     inc    dx            ; 07: 10's of day of month
  2351.     in    al,dx
  2352.     and    al, 0Fh
  2353.     cmp    al,3
  2354.     ja    CLK_check_no_MSM6242
  2355.     inc    dx            ; 08: 1's of month number
  2356.     in    al,dx
  2357.     and    al, 0Fh
  2358.     cmp    al,10
  2359.     ja    CLK_check_no_MSM6242
  2360.     inc    dx            ; 09: 10's of month number
  2361.     in    al,dx
  2362.     and    al, 0Fh
  2363.     cmp    al,1
  2364.     ja    CLK_check_no_MSM6242
  2365.     inc    dx            ; 0A: 1's of year (within century)
  2366.     in    al,dx
  2367.     and    al, 0Fh
  2368.     cmp    al,10
  2369.     ja    CLK_check_no_MSM6242
  2370.     inc    dx            ; 0B: 10's of year
  2371.     in    al,dx
  2372.     and    al, 0Fh
  2373.     cmp    al,10
  2374.     ja    CLK_check_no_MSM6242
  2375.     inc    dx            ; 0C: day of week (Sun=1, Sat=7))
  2376.     in    al,dx
  2377.     and    al, 0Fh
  2378.     jz    CLK_check_no_MSM6242
  2379.     cmp    al,7
  2380.     ja    CLK_check_no_MSM6242
  2381. ;
  2382. ; It certainly looks like an MSM6242 clock chip.
  2383. ; Return with carry flag clear.
  2384. ;
  2385.     clc
  2386.     debug_message '(ok) } '
  2387.     ret
  2388. CLK_check_no_MSM6242:
  2389. ;
  2390. ; It is definitely not an MSM6242 clock chip.
  2391. ; Return with carry flag set.
  2392. ;
  2393.     stc
  2394.     debug_message '( failed ) } '
  2395.     ret
  2396. CLK_check_MSM6242 endp
  2397.  
  2398. startup_code    ends
  2399.  
  2400. resident_code    segment
  2401.  
  2402. ;
  2403. ; Get the time from clock chip
  2404. ;
  2405.  
  2406. CLK_get_MSM6242_time proc near
  2407.     assume ds:cgroup
  2408.     debug_message '{ get MSM6242 '
  2409. ;
  2410. ; Read all the relevant values from the chip's registers
  2411. ;
  2412.     mov    [CLK_time_hsec], 0    ; no hundredths available
  2413.     mov    dx,[CLK_chip_IO_base]    ; Get chip base address
  2414.     in    al,dx            ; 00: 1's of seconds
  2415.     mov    ah, al
  2416.     inc    dx            ; 01: 10's of seconds
  2417.     in    al, dx
  2418.     xchg    ah, al
  2419.     and    ax, 0F0Fh   ; ignore junk bits
  2420.     aad            ; AAD sets AL := AL + 10*AH.
  2421.                 ; Now if Intel would just *document* the
  2422.                 ; extension to bases other than 10, we
  2423.                 ; could use it for other interesting things...
  2424.     debug_message '( secs '
  2425.     debug_hex_byte al
  2426.     debug_message ' ) '
  2427.     mov    [CLK_time_sec],al
  2428.     inc    dx            ; 02: 1's of minutes
  2429.     in    al,dx
  2430.     mov    ah, al
  2431.     inc    dx            ; 03: 10's of minutes
  2432.     in    al,dx
  2433.     xchg    ah, al
  2434.     and    ax, 0F0Fh
  2435.     aad
  2436.     debug_message '( mins '
  2437.     debug_hex_byte al
  2438.     debug_message ' ) '
  2439.     mov    [CLK_time_min],al
  2440.     inc    dx            ; 04: 1's of hours
  2441.     in    al,dx
  2442.     inc    dx            ; 05: 10's of hours
  2443.     in    al,dx
  2444.     mov    ah, al
  2445.     xchg    ah, al
  2446.     and    ax, 0F0Fh
  2447.     aad
  2448.     debug_message '( hour '
  2449.     debug_hex_byte al
  2450.     debug_message ' ) '
  2451.     mov    [CLK_time_hour],al
  2452.     inc    dx            ; 06: 1's of day of month
  2453.     in    al,dx
  2454.     mov    ah, al
  2455.     inc    dx            ; 07: 10's of day of month
  2456.     in    al,dx
  2457.     xchg    ah, al
  2458.     and    ax, 0F0Fh
  2459.     aad
  2460.     debug_message '( day '
  2461.     debug_hex_byte al
  2462.     debug_message ' ) '
  2463.     mov    [CLK_date_day],al
  2464.     inc    dx            ; 08: 1's of month number
  2465.     in    al,dx
  2466.     mov    ah, al
  2467.     inc    dx            ; 09: 10's of month number
  2468.     in    al,dx
  2469.     xchg    ah, al
  2470.     and    ax, 0F0Fh
  2471.     aad
  2472.     debug_message '( month '
  2473.     debug_hex_byte al
  2474.     debug_message ' ) '
  2475.     mov    [CLK_date_month],al
  2476.     inc    dx            ; 0A: 1's of year (within century)
  2477.     in    al,dx
  2478.     mov    ah, al
  2479.     inc    dx            ; 0B: 10's of year
  2480.     in    al,dx
  2481.     xchg    ah, al
  2482.     and    ax, 0F0Fh
  2483.     aad
  2484.     debug_message '( year '
  2485.     debug_hex_byte al
  2486.     debug_message ' ) '
  2487.     mov    [CLK_date_year],al
  2488.     ;inc    dx            ; 0C: day of week (Sun=1,Sat=7) (ignore)
  2489. ;
  2490. ; Choose a suitable century
  2491. ;
  2492.     mov    ah,19            ; Assume the year is 19xx.
  2493.     cmp    al,80            ; If last two digits less than 80
  2494.                     ; then the year is 20xx
  2495.     jnb    CLK_get_MSM6242_century
  2496.     inc    ah
  2497. CLK_get_MSM6242_century:
  2498.     debug_message '( century '
  2499.     debug_hex_byte ah
  2500.     debug_message' ) '
  2501.     mov    [CLK_date_century],ah
  2502. ;
  2503. ; Convert the date to number of days since 1-Jan-1980
  2504. ;
  2505.     call CLK_conv_ccyymmdd_days
  2506. ;
  2507. ; Finished getting time from chip
  2508. ;
  2509.     ret
  2510.     debug_message '} '
  2511. CLK_get_MSM6242_time endp
  2512.  
  2513. ;
  2514. ; Set the time on the clock chip.
  2515. ;
  2516.  
  2517. CLK_set_MSM6242_time proc near
  2518.     assume ds:cgroup
  2519.     debug_message '{ set MSM6242 '
  2520. ;
  2521. ; Convert date to correct format
  2522. ;
  2523.     call CLK_conv_days_ccyymmdd    ; Year, month, day from day number
  2524.     call CLK_conv_days_weekday    ; Find current day of week
  2525. ;
  2526. ; Store data into clock chip
  2527. ;
  2528.     mov    dx,[CLK_chip_IO_base]    ; Get chip base address
  2529.     mov    al,[CLK_time_sec]    ; 00 and 01: secs
  2530.     debug_message '( secs '
  2531.     debug_hex_byte al
  2532.     debug_message ') '
  2533.     aam    ; puts high digit in AH, low digit in AL
  2534.     out    dx, al
  2535.     inc    dx
  2536.     mov    al, ah
  2537.     out    dx, al
  2538.     inc    dx
  2539.     mov    al,[CLK_time_min]    ; 02 and 03: mins
  2540.     debug_message '( mins '
  2541.     debug_hex_byte al
  2542.     debug_message ') '
  2543.     aam
  2544.     out    dx, al
  2545.     inc    dx
  2546.     mov    al, ah
  2547.     out    dx, al
  2548.     inc    dx
  2549.     mov    al,[CLK_time_min]    ; 04 and 05: hours
  2550.     debug_message '( hours '
  2551.     debug_hex_byte al
  2552.     debug_message ') '
  2553.     aam
  2554.     out    dx, al
  2555.     inc    dx
  2556.     mov    al, ah
  2557.     out    dx, al
  2558.     inc    dx
  2559.     mov    al,[CLK_date_day]    ; 06 and 07: day of month
  2560.     debug_message '( day '
  2561.     debug_hex_byte al
  2562.     debug_message ') '
  2563.     aam
  2564.     out    dx, al
  2565.     inc    dx
  2566.     mov    al, ah
  2567.     out    dx, al
  2568.     inc    dx
  2569.     mov    al,[CLK_date_month]    ; 08 and 09: month
  2570.     debug_message '( month '
  2571.     debug_hex_byte al
  2572.     debug_message ') '
  2573.     aam
  2574.     out    dx, al
  2575.     inc    dx
  2576.     mov    al, ah
  2577.     out    dx, al
  2578.     inc    dx
  2579.     mov    al,[CLK_date_year]    ; 0A and 0B: year
  2580.     debug_message '( year '
  2581.     debug_hex_byte al
  2582.     debug_message ') '
  2583.     aam
  2584.     out    dx, al
  2585.     inc    dx
  2586.     mov    al, ah
  2587.     out    dx, al
  2588.     inc    dx
  2589.     mov    al,[CLK_date_weekday]    ; 0C: day of week
  2590.     inc    al            ; Chip uses Sunday=1, not =0
  2591.     debug_message '( weekday '
  2592.     debug_hex_byte al
  2593.     debug_message ' ) '
  2594.     out    dx,al
  2595. ;
  2596. ; Finished
  2597. ;
  2598.     ret
  2599.     debug_message '} '
  2600. CLK_set_MSM6242_time endp
  2601.  
  2602. resident_code    ends
  2603.  
  2604. ;***********************************************************************
  2605.  
  2606. ;
  2607. ; Subroutines to read and set the BIOS timer.
  2608. ;
  2609. ; All these routines are in the resident_code.
  2610. ;
  2611. ; There are calibration values in the resident_data that may (one day)
  2612. ; be controllable from the command line in the CONFIG.SYS file.  At present
  2613. ; they are fixed at assembly time.
  2614. ;
  2615.  
  2616. resident_data    segment
  2617.  
  2618. ;
  2619. ; These values are used to calibrate the BIOS timer.
  2620. ; CLK_tick_65536 should contain the number of ticks that will occur
  2621. ; in 65536 seconds.  This should be 1193180 (hex 001234DC), because
  2622. ; the clock input to the 8253 timer chip is 1.19318 MHz.
  2623. ; CLK_tick_65536_20 is one twentieth of the number of ticks per 65536
  2624. ; seconds.  It should be 59659 (hex E90B).
  2625. ; CLK_tick_day should contain the number of ticks per day.  This should
  2626. ; be 1573040 (hex 001800B0).
  2627. ; All these values should be changed simultaneously! There is no check
  2628. ; to ensure that they are self-consistent.
  2629. ; Note that unless the BIOS timer interrupt server is modified, changing the
  2630. ; ticks per day value here will not have the desired effect.
  2631. ; Even if these values are modified, some sections of the code assume that
  2632. ; the number of ticks per second is 18.2.  For this reason, small adjustments
  2633. ; for calibration purposes are OK, but large adjustments may cause trouble.
  2634. ;
  2635. CLK_tick_65536         dd  1193180     ; Number of ticks in 65536 seconds
  2636. CLK_tick_65536_20    dw    59659     ; CLK_tick_65536 divided by 20
  2637. CLK_tick_day         dd  1573040     ; Number of time-base ticks per day
  2638.  
  2639. ;
  2640. ; Keep the last BIOS tick count, to check for time going backwards.
  2641. ; The initial value of zero is chosen so that the first time
  2642. ; CLK_get_BIOS_time is called, it doesn't think time has gone backwards.
  2643. ;
  2644. CLK_last_tick         dd      0
  2645.  
  2646. ;
  2647. ; The last date, hour and minute at which the timer was called.
  2648. ; This is needed when the AT BIOS or some other coarse graoned clock
  2649. ; is used.
  2650. ; We don't bother updating this data if a fine grain clock
  2651. ; chip is used.
  2652. ;
  2653. ; When we have to deal with a coarse grain clock chip, we can do better
  2654. ; by usually using the normal BIOS timer (to get finer granularity)
  2655. ; and whenever the get-time procedure is called more than a few minutes
  2656. ; after the previous call, or when the date has changed, we update the
  2657. ; BIOS timer from the chip, to get reasonable long term accuracy.
  2658. ; The initial values in the DW's below ensure that the genuine chip
  2659. ; clock is used the first time.
  2660. ;
  2661. CLK_last_date    dw    -10    ; The date
  2662. CLK_last_time    dw    -10    ; Hours*60 + minutes
  2663.  
  2664. resident_data    ends
  2665.  
  2666. resident_code    segment
  2667.  
  2668. ;
  2669. ; Make the BIOS timer correspond to our time.
  2670. ;
  2671. CLK_set_BIOS_time proc near
  2672.         assume ds:cgroup
  2673.         debug_message '{ set BIOS '
  2674. ;
  2675. ; First convert the time to a number of seconds.  Ignore the hundredths
  2676. ; of seconds until later.
  2677. ;
  2678.         mov  al,CLK_time_hour        ; Multiply hours by 60
  2679.         debug_message '( hour '
  2680.         debug_hex_byte al
  2681.         debug_message ' ) '
  2682.         mov  bx,60
  2683.         mul  bl
  2684.         mov  cl,CLK_time_min        ; Add minutes
  2685.         debug_message '( min '
  2686.         debug_hex_byte cl
  2687.         debug_message ' ) '
  2688.         xor  ch,ch
  2689.         add  ax,cx
  2690.         ; Now AX is number of minutes
  2691.         mul  bx             ; Multiply total minutes by 60
  2692.         mov  cl,CLK_time_sec        ; Add seconds
  2693.         debug_message '( sec '
  2694.         debug_hex_byte cl
  2695.         debug_message ' ) '
  2696.         add  ax,cx
  2697.         adc  dx,0
  2698.         ; Combined DX_AX is now the number of seconds.
  2699.         debug_message '( total secs '
  2700.         debug_hex_word dx
  2701.         debug_hex_word ax
  2702.         debug_message ' ) '
  2703. ;
  2704. ; Convert the number of seconds to a number of ticks.
  2705. ; This requires multiplying by 1193180, then dividing by 65536.
  2706. ;
  2707. ; This is done by multiplying the 32-bit value in DX_AX by the 21-bit
  2708. ; value 1193180 (hexadecimal 001234DC), and discarding the lowest 16 bits
  2709. ; of the result.  (This works because 65536 is 2^16.)
  2710. ;
  2711.         ;
  2712.         ; The value in DX_AX can never be greater than 24*60*60,
  2713.         ; which is 86400, or hexadecimal 00015180.  After
  2714.         ; multiplication by 1193180, the result will not be larger
  2715.         ; than about 103e9, which is representable in 37 bits.
  2716.         ; As the result will then be divided by 65536 (which is 2^16),
  2717.         ; we need not even calculate the lowest 16 bits.  The result
  2718.         ; will therefore require 21 bits of storage, and 32 bits
  2719.         ; will actually be used.
  2720.         ;
  2721.         ; Let the low 16 bits of the multiplicand (now in DX_AX)
  2722.         ; be A0.  Let the high 16 bits be A1.
  2723.         ; Let the low 16 bits of the multiplier (1193180) be B0.
  2724.         ; Let the high 16 bits be B1.
  2725.         ; In general, multiplying two 32-bit values could yield
  2726.         ; a 64-bit product.
  2727.         ; Let the four 16-bit sections of the product be C0, C1,
  2728.         ; C2 and C3, with C0 being the lowest 16 bits.
  2729.         ; Let L() and H() denote the low and high 16 bits of a
  2730.         ; 32-bit value.
  2731.         ; Let D0, D1, D2 and D3 be 32-bit intermediate results.
  2732.         ; Note that C3 will be zero.
  2733.         ; C0 will be ignored, unless it is greater than 2^15, in
  2734.         ; which case the total will be rounded up.
  2735.         ;
  2736.         ; D0 = A0*B0
  2737.         ; C0 = L( D0 )
  2738.         ; D1 = H( D0 ) + L( A0*B1 ) + L( A1*B0 )
  2739.         ; C1 = L( D1 )
  2740.         ; D2 = H( D1 ) + H( A0*B1 ) + H( A1*B0 ) + L( A1*B1 )
  2741.         ; C2 = L( D2 )
  2742.         ; D3 = H( D2 ) + H( A1*B1 )
  2743.         ; C3 = L( D3 )
  2744.         ;
  2745.         ; C3 will be zero, so D3 need not be calculated.
  2746.         ;
  2747.         ; Save the value currently in DX_AX
  2748.         mov  t1,dx                ; T1 := A1
  2749.         mov  t2,ax                ; T2 := A0
  2750.         ;
  2751.         ; Calculate D0 = A0*B0
  2752.                             ; AX is already = A0
  2753.         mov  bx,word ptr [CLK_tick_65536]    ; BX := B0
  2754.         mul  bx
  2755.         ;
  2756.         ; We don't need L(D0), which is in AX, but we must
  2757.         ; save H(D0), which is in DX.  Before doing so, check
  2758.         ; if L(D0) is greater that hex 7FFF, in which case the
  2759.         ; result must be rounded up by incrementing DX.  Note that
  2760.         ; DX cannot be larger than hex FFFE, so incrementing it
  2761.         ; here cannot cause overflow problems.
  2762.         mov  cx,7FFFh
  2763.         cmp  cx,ax            ; Set carry flag if AX > 7FFFh
  2764.         adc  dx,0            ; Increment DX if necessary
  2765.         mov  t3,dx            ; T3 := H(D0)
  2766.         ;
  2767.         ; Multiply A1*B0
  2768.         mov  ax,t1            ; AX := A1
  2769.                         ; BX is already = B0
  2770.         mul  bx
  2771.         mov  t4,dx            ; T4 := H(A1*B0)
  2772.         mov  t5,ax            ; T5 := L(A1*B0)
  2773.         ;
  2774.         ; Multiply A0*B1
  2775.         mov  ax,t2                 ; AX := A0
  2776.         mov  bx,word ptr [CLK_tick_65536+2]  ; BX := B1
  2777.         mul  bx
  2778.         mov  t2,dx                 ; T2 := H(A0*B1)
  2779.         ;
  2780.         ; Calculate D1 = H(D0) + L(A0*B1) + L(A1*B0)
  2781.                         ; AX is already = L(A0*B1)
  2782.         xor  dx,dx
  2783.         add  ax,t3            ; add H(D0)
  2784.         adc  dx,0
  2785.         add  ax,t5            ; add L(A1*B0)
  2786.         adc  dx,0
  2787.         mov  t3,dx            ; T3 := H(D1)
  2788.         mov  t5,ax            ; T5 := C1 { = L(D1) }
  2789.         ;
  2790.         ; Multiply A1*B1
  2791.         mov  ax,t1            ; AX := A1
  2792.                         ; BX is already = B1
  2793.         mul  bx
  2794.         ;
  2795.         ; H(A1*B1) should be zero, and is not needed.
  2796.         ; Calculate D2 = H(D1) + L(A1*B1) + H(A1*B0) + H(A0*B1).
  2797.         ; H(D2) will be zero, and need not be calculated.
  2798.                         ; AX is already = L(A1*B1)
  2799.         add  ax,t3            ; add H(D1)
  2800.         add  ax,t4            ; add H(A1*B0)
  2801.         add  ax,t2            ; add H(A0*B1)
  2802.         mov  cx,ax            ; CX := L(D2) { = C2 }
  2803.         ;
  2804.         ; CX is high word, T5 is low word of result
  2805.         debug_message '( ticks from secs '
  2806.         debug_hex_word cx
  2807.         debug_hex_word t5
  2808.         debug_message ' ) '
  2809. ;
  2810. ; We now have a number of clock ticks, but it ignores the hundredths of
  2811. ; seconds.  Treating the hundredths of seconds exactly would have required
  2812. ; multiplying a 32-bit value by 100 and dividing a 32-bit value by 100, in
  2813. ; addition to the code already used.  That task is not difficult to code
  2814. ; but it does add much extra code for very little benefit.
  2815. ;
  2816. ; An approximate number of extra ticks is added to the count now, to handle
  2817. ; the hundredths of seconds.  This approximate value is obtained simply by
  2818. ; multiplying the hundredths by 10 and dividing by 55.  Just before dividing
  2819. ; by 55, add 27 to make the division round instead of truncating.
  2820. ;
  2821.         mov  al,CLK_time_hsec        ; Calculate ticks for centi-secs
  2822.         debug_message '( centi '
  2823.         debug_hex_byte al
  2824.         debug_message ' ) '
  2825.         mov  ah,10
  2826.         mul  ah
  2827.         add  ax,27
  2828.         mov  bl,55
  2829.         div  bl
  2830.         xor  ah,ah            ; Discard the remainder
  2831.         add  ax,t5            ; Add to total
  2832.         adc  cx,0            ;
  2833.         ; Now the total number of ticks is in CX_AX
  2834. ;
  2835. ; Remember the tick count for later
  2836. ;
  2837.         mov word ptr ds:[CLK_last_tick+2], cx
  2838.         mov word ptr ds:[CLK_last_tick], ax
  2839. ;
  2840. ; Call the BIOS "set timer" interrupt
  2841. ;
  2842.         mov  dx,ax            ; DX is low 16 bits of count
  2843.                         ; CX is already high 16 bits
  2844.         debug_message '( total ticks '
  2845.         debug_hex_cx_dx
  2846.         debug_message ' ) '
  2847.         mov  ah,1            ; INT 1Ah function 1
  2848.         int  1Ah            ; make BIOS set its timer
  2849. ;
  2850. ; Finished, at last
  2851. ;
  2852.         debug_message '} '
  2853.         ret
  2854. CLK_set_BIOS_time endp
  2855.  
  2856. ;
  2857. ; Make our time correspond to the BIOS timer.
  2858. ;
  2859. CLK_get_BIOS_time proc near
  2860.         assume ds:cgroup
  2861.         debug_message '{ get BIOS '
  2862. ;
  2863. ; Call the BIOS "get timer" interrupt
  2864. ;
  2865.         xor  ah,ah            ; INT 1Ah function 0
  2866.         int  1Ah            ;
  2867.         debug_message '( total ticks '
  2868.         debug_hex_cx_dx
  2869.         debug_message ' ) '
  2870.         ; Tick count is now in combined CX_DX
  2871. ;
  2872. ; If time seems to have run backwards (AL=0 means date didn't change, but
  2873. ; current tick count is less than remembered tick count) then assume the
  2874. ; date changed.
  2875. ;
  2876.         or  al,al            ; if AL <> 0 then
  2877.         jnz CLK_get_BIOS_date_change    ; the date has changed
  2878.         cmp cx, word ptr ds:[CLK_last_tick+2] ; check high word
  2879.         ja  CLK_get_BIOS_date_nochange    ; time went forwards
  2880.         jb  CLK_get_BIOS_time_backwards ; time went backwards
  2881.         cmp dx, word ptr ds:[CLK_last_tick] ; check low word
  2882.         jnb CLK_get_BIOS_date_nochange    ; time went forwards
  2883. CLK_get_BIOS_time_backwards:
  2884.         mov al, 1            ; assume date changed
  2885. ;
  2886. ; Increment the date if necessary.
  2887. ;
  2888. ; NOTE: Just add AL to the current date.  AL is guaranteed to be zero if
  2889. ; the date has not changed.  On the IBM PC, AL will be 1 if the date has
  2890. ; changed (even if the date has changed more than once).  With a non-IBM
  2891. ; BIOS, AL might have some other value.  The best possible situation is
  2892. ; AL = (number of times the date has changed).  The worst possible situation
  2893. ; is AL = (some arbitrary non-zero value).
  2894. ;
  2895. CLK_get_BIOS_date_change:
  2896.         xor  ah,ah
  2897.         add  CLK_date_days, ax
  2898. CLK_get_BIOS_date_nochange:
  2899. ;
  2900. ; Remember the tick count for next time
  2901. ;
  2902.         mov word ptr ds:[CLK_last_tick+2],cx
  2903.         mov word ptr ds:[CLK_last_tick],dx
  2904. ;
  2905. ;
  2906. ; If for some reason the BIOS tick count is too large, adjust it to the
  2907. ; maximum number of ticks per day.
  2908. ;
  2909.         cmp  cx,word ptr ds:[CLK_tick_day+2]    ; Check high word
  2910.         jb   CLK_get_BIOS_ok
  2911.         ja   CLK_get_BIOS_too_big
  2912.         cmp  dx,word ptr ds:[CLK_tick_day]    ; Check high word
  2913.         jb   CLK_get_BIOS_ok
  2914. CLK_get_BIOS_too_big:
  2915.         mov  cx,word ptr ds:[CLK_tick_day+2]    ; Set to max count
  2916.         mov  dx,word ptr ds:[CLK_tick_day]
  2917.         dec  dx                 ; And subtract 1
  2918.         sbb  cx,0
  2919.         debug_message '( total adjusted to '
  2920.         debug_hex_cx_dx
  2921.         debug_message ' ) '
  2922. CLK_get_BIOS_ok:
  2923. ;
  2924. ; Now convert the tick count in CX_DX to a number of seconds.
  2925. ; This requires multiplying by 65536 and dividing by 1193180.
  2926. ; The remainder after the division will be used to determine the number
  2927. ; of hundredths of seconds.
  2928. ;
  2929. ; Multiplication by 65536 is done simply by appending sixteen zero bits
  2930. ; to the result (i.e., by shifting the result left 16 bits).  This works
  2931. ; because 65536 is 2^16.  The 48-bit result (which will actually never be
  2932. ; larger than hex 001800B00000) is then divided by the 32-bit value
  2933. ; 1193180 (hex 001234DC).
  2934. ;
  2935. ; Dividing by a 32-bit value is complicated, but 1193180 factorises into
  2936. ; 59659*20.  It is then much simpler to divide by 59659, which can be
  2937. ; represented in 16 bits.  The result of this division will be a 32-bit
  2938. ; value (no larger than hex 001A5E00), representing twenty times the
  2939. ; number of seconds.
  2940. ;
  2941.         ;
  2942.         ; Let A0, A1 and A2 be the three 16-bit portions making
  2943.         ; up the dividend.  A0, the least significant 16 bits,
  2944.         ; will be zero.  A1 and A2 are currently stored in DX and
  2945.         ; CX, as returned by the BIOS.
  2946.         ; Let B be the 16-bit divisor.
  2947.         ; Let C0, C1 and C2 be three 16-bit sections of the quotient.
  2948.         ; (In fact, C2 will be zero).
  2949.         ; Let D be the 16-bit remainder.
  2950.         ; Let (Q0,R0), (Q1,R1) and (Q2,R2) be pairs of 16-bit
  2951.         ; quotients and remainders resulting from division of a
  2952.         ; 32-bit dividend by a 16-bit divisor.
  2953.         ;
  2954.         ; (Q2,R2) = A2 / B
  2955.         ; C2 = Q2
  2956.         ; A2 will be less than B, so C2 = 0 and R2 = A2
  2957.         ; (Q1,R1) = (2^16*R2 + A1) / B
  2958.         ; C1 = Q1
  2959.         ; (Q0,R0) = (2^16*R1 + A0) / B
  2960.         ; C0 = Q0
  2961.         ; D = R0
  2962.         ;
  2963.         ; There is no need to calculate (Q2,R2).
  2964.         ; Calculate (Q1,R1) now.
  2965.         mov  ax,dx            ; AX := A1
  2966.         mov  dx,cx            ; DX := A2
  2967.         mov  bx,[CLK_tick_65536_20]
  2968.         div  bx
  2969.         mov  cx,ax            ; CX := Q1 { = C1 }
  2970.         ;
  2971.         ; Calculate (Q0,R0)
  2972.         xor  ax,ax            ; AX := A0 { = 0 }
  2973.         div  bx
  2974.         ;
  2975.         ; Now C0 is in AX, C1 is in CX and D is in DX.
  2976. ;
  2977. ; We now have the number of twentieths of seconds.
  2978. ; Divide by twenty to get number of seconds, and multiply the
  2979. ; remainder from that division by 5 to get the number of extra
  2980. ; hundredths.
  2981. ;
  2982.         ;
  2983.         ; This is basically the same as the previous long division,
  2984.         ; except that the dividend is only 32 bits, and the low part
  2985.         ; is not zero.
  2986.         ;
  2987.         ; Calculate (Q1,R1)
  2988.         xchg ax,cx            ; AX := A1, CX := A0
  2989.         xor  dx,dx            ; DX := A2 = 0
  2990.         mov  bx,20
  2991.         div  bx
  2992.         ;
  2993.         ; Calculate (Q0,R0)
  2994.         xchg ax,cx            ; CX := Q1 { = C1 }, AX := A0
  2995.         div  bx
  2996.         ;
  2997.         ; Now C0 is in AX, C1 is in CX and D is in DX.
  2998.         debug_message '( integer secs '
  2999.         debug_hex_word cx
  3000.         debug_hex_word ax
  3001.         debug_message ' spare ticks '
  3002.         debug_hex_word dx
  3003.         debug_message ' ) '
  3004. ;
  3005. ; Multiply the remainder (the number of spare twentieths) by 5
  3006. ; to get the number of spare hundredths
  3007. ;
  3008.         xchg ax,dx            ; AX := remainder, DX := C0
  3009.         mov  ah,5
  3010.         mul  ah
  3011.         debug_message '( centi '
  3012.         debug_hex_byte al
  3013.         debug_message ' ) '
  3014.         mov  CLK_time_hsec,al        ; Store the number of hundredths
  3015. ;
  3016. ; Divide by 60 to get the total number of minutes.  The remainder will
  3017. ; be the number of loose seconds.
  3018. ;
  3019.         mov  ax,dx            ; AX := low 16 bits
  3020.         mov  dx,cx            ; DX := high 16 bits
  3021.         mov  bx,60
  3022.         div  bx             ; 32-bit dividend
  3023.         debug_message '( integer mins '
  3024.         debug_hex_word ax
  3025.         debug_message ' remainder '
  3026.         debug_hex_word dx
  3027.         debug_message ' ) '
  3028.         debug_message '( secs '
  3029.         debug_hex_byte dl
  3030.         debug_message ' ) '
  3031.         mov  CLK_time_sec,dl        ; Remainder is num. seconds
  3032. ;
  3033. ; Divide by 60 to get the number of hours.  The remainder will be
  3034. ; the number of minutes.
  3035. ;
  3036.         div  bl             ; 16-bit dividend
  3037.         debug_message '( min '
  3038.         debug_hex_byte ah
  3039.         debug_message ' ) '
  3040.         debug_message '( hour '
  3041.         debug_hex_byte al
  3042.         debug_message ' ) '
  3043.         mov  CLK_time_min,ah        ; Remainder is num. minutes
  3044.         mov  CLK_time_hour,al        ; Quotient is num hours
  3045. ;
  3046. ; If everything worked correctly, the number of hours will not exceed 23.
  3047. ; If there is a bug, it could be fixed by testing for 24 hours here, and
  3048. ; setting the time to 23:59:59.99 instead.
  3049. ;
  3050. ; Finished, at last
  3051. ;
  3052.         debug_message '} '
  3053.         ret
  3054. CLK_get_BIOS_time endp
  3055.  
  3056. resident_code    ends
  3057.  
  3058. ;***********************************************************************
  3059.  
  3060. ;
  3061. ; Look for a clock chip of whatever type.
  3062. ; On entry, AL says what to do:
  3063. ;   0:    Don't bother doing anything.
  3064. ;   FFh or FEh:  Look for all types of clock chip.
  3065. ;   other: ASCII character saying what type of chip to look for.
  3066. ; On exit, Carry flag and AL say what was found:
  3067. ;   CF set:  No clock chip
  3068. ;   CF clear, AL=ASCII character saying what type of chip was found.
  3069. ; If a chip was found, other data (such as CLK_chip_IO_base) will also
  3070. ; have been set, as required by that chip.
  3071. ;
  3072.  
  3073. startup_code    segment
  3074.  
  3075. CLK_find_chip    proc near
  3076.         or  al, al
  3077.         jz  CLK_find_no_chip    ; don't bother if zero
  3078.         jl  CLK_find_any_chip    ; try all types if less than zero
  3079.         push ax         ; remember type for later
  3080.         cmp al, '1'        ; try MM58167AN ?
  3081.         jne CLK_find_chip_2
  3082.         call CLK_find_MM58167AN
  3083.         jmp CLK_find_chip_pop_ax
  3084. CLK_find_chip_2:
  3085.         cmp al, '2'        ; try MSM6242 ?
  3086.         jne CLK_find_no_chip
  3087.         call CLK_find_MSM6242
  3088. ;
  3089. ; Here when chip might or might not have been found, and AX
  3090. ; needs to be popped before returning to caller.
  3091. ;
  3092. CLK_find_chip_pop_ax:
  3093.         pop ax
  3094.         ret
  3095. ;
  3096. ; Here to try all types of chip
  3097. ;
  3098. CLK_find_any_chip:
  3099.         call CLK_find_MM58167AN     ; try MM58167AN
  3100.         mov al, '1'
  3101.         jnc CLK_find_chip_done        ; yes ?
  3102.         call CLK_find_MSM6242        ; try MSM6242
  3103.         mov al, '2'
  3104.         jnc CLK_find_chip_done        ; yes?
  3105. ;
  3106. ; Here when chip was not found
  3107. ;
  3108. CLK_find_no_chip:
  3109.         stc
  3110. CLK_find_chip_done:
  3111.         ret
  3112. CLK_find_chip    endp
  3113.  
  3114. startup_code    ends
  3115.  
  3116. ;***********************************************************************
  3117.  
  3118. ;
  3119. ; Several display output functions intended mainly debugging.  Some may also
  3120. ; be used by the startup routine.
  3121. ;
  3122.  
  3123. ;
  3124. ; The display_message function is used by the startup routine,
  3125. ; so must be in the resident_code segment if debugging is enabled,
  3126. ; or in the startup_code segment if debugging is disabled.
  3127. ; The same applies to the display_char routine.
  3128. ;
  3129. if do_debug_writes
  3130. resident_code    segment
  3131. else
  3132. startup_code    segment
  3133. endif ; do_debug_writes
  3134.  
  3135. ;
  3136. ; Display the single character in AL.
  3137. ;
  3138. display_char    proc near
  3139. ;
  3140. ; Just tell BIOS to display it, in the normal colour.
  3141. ;
  3142.     mov    ah,0Eh
  3143.     mov    bx,1
  3144.     int    10h
  3145.     ret
  3146. display_char    endp
  3147.  
  3148. ;
  3149. ; Display a message pointed to by DS:SI, with length in CX
  3150. ;
  3151. display_message proc near
  3152. ;
  3153. ; Save registers
  3154. ;
  3155.         pushf
  3156.         push ax
  3157.         push bx
  3158.         push cx
  3159.         push si
  3160. ;
  3161. ; Loop displaying one byte at a time until count gets to zero
  3162. ;
  3163.         cld
  3164. display_msg_loop:
  3165.         lodsb
  3166.     call    display_char
  3167.         loop display_msg_loop
  3168. ;
  3169. ; Restore all modified registers, and return.
  3170. ;
  3171.         pop  si
  3172.         pop  cx
  3173.         pop  bx
  3174.         pop  ax
  3175.         popf
  3176.         ret
  3177. display_message endp
  3178.  
  3179. if do_debug_writes
  3180. resident_code    ends
  3181. else
  3182. startup_code    ends
  3183. endif ; do_debug_writes
  3184.  
  3185. ;
  3186. ; All the remaining functions in this section are used for debugging only
  3187. ;
  3188.  
  3189. if do_debug_writes
  3190. resident_code    segment
  3191.  
  3192. ;
  3193. ; Display the number in AL as a single hexadecimal digit
  3194. ;
  3195. display_hex_digit proc near
  3196. ;
  3197. ; Save registers
  3198. ;
  3199.         pushf
  3200.         push ax
  3201.         push bx
  3202. ;
  3203. ; Convert and display the digit
  3204. ;
  3205.         and  al,0Fh
  3206.         add  al,'0'
  3207.         cmp  al,'9'
  3208.         jbe  display_hex_output
  3209.         add  al,'A'-0Ah-'0'
  3210. display_hex_output:
  3211.     call    display_char
  3212. ;
  3213. ; Restore registers and return
  3214. ;
  3215.         pop  bx
  3216.         pop  ax
  3217.         popf
  3218.         ret
  3219. display_hex_digit endp
  3220.  
  3221. ;
  3222. ; Display a byte passed in AL, as two hexadecimal digits.
  3223. ;
  3224. display_hex_byte proc near
  3225. ;
  3226. ; Save registers
  3227. ;
  3228.         pushf
  3229.         push ax
  3230.         push cx
  3231. ;
  3232. ; Isolate and display high digit
  3233. ;
  3234.         mov  ah,al
  3235.         mov  cl,4
  3236.         shr  al,cl
  3237.         call display_hex_digit
  3238. ;
  3239. ; Isolate and display low digit
  3240. ;
  3241.         mov  al,ah
  3242.         call display_hex_digit
  3243. ;
  3244. ; Restore registers and return
  3245. ;
  3246.         pop  cx
  3247.         pop  ax
  3248.         popf
  3249.         ret
  3250. display_hex_byte endp
  3251.  
  3252. ;
  3253. ; Display several hex bytes, separated by blanks.
  3254. ; Address passed in DS:SI, with count in CX.
  3255. ;
  3256. display_hex_bytes proc near
  3257. ;
  3258. ; Save registers
  3259. ;
  3260.         pushf
  3261.         push ax
  3262.         push bx
  3263.         push cx
  3264.         push di
  3265. ;
  3266. ; Loop displaying bytes
  3267. ;
  3268.         cld
  3269.         mov  bx,1
  3270. display_bytes_loop:
  3271.         lodsb
  3272.         call display_hex_byte
  3273.         mov  al,' '
  3274.     call    display_char
  3275.         loop display_bytes_loop
  3276. ;
  3277. ; Restore regs and return
  3278. ;
  3279.         pop  di
  3280.         pop  cx
  3281.         pop  bx
  3282.         pop  ax
  3283.         popf
  3284.         ret
  3285. display_hex_bytes endp
  3286.  
  3287. resident_code    ends
  3288. endif ; do_debug_writes
  3289.  
  3290. ;***********************************************************************
  3291.  
  3292. ;
  3293. ; This is the main initialisation code for the CLOCK$ device.
  3294. ;
  3295. ; It prints the initialisation message, processes command line
  3296. ; options (from the DEVICE=... line in the CONFIG.SYS file),
  3297. ; installs pointers to suitable time get and set ruotines (based on the
  3298. ; command line options and the presence or absence of suitable
  3299. ; hardware), and finally returns a pointer to the end of the
  3300. ; permanently resident memory.
  3301. ;
  3302. ; Because the total size of the resident code is small (about 1.5K),
  3303. ; there is no great need to free the space used by routines that deal
  3304. ; with timers that are not present.  The code to handle a clock chip thus
  3305. ; remains permanently resident even on a machine with no clock chip, etc.
  3306. ; (Or, if you prefer, I am just too lazy to implement a method of dynamic
  3307. ; relocation so that unused code and data can be freed.)
  3308. ;
  3309.  
  3310. startup_data    segment
  3311.  
  3312. ;
  3313. ; Various flags determined from the command line arguments and from
  3314. ; tests done during the installation process.
  3315. ; For most of these flags, the default value of 0FFh means the
  3316. ; program must determine the correct value by performing tests
  3317. ; during the initialisation.  Values 0 and 1 mean respectively
  3318. ; NO and YES.  These values may be set by command line arguments or
  3319. ; by tests done during the initialisation.
  3320. ;
  3321. CLK_use_AT_BIOS     db 0FFh ; Use AT-style real time clock BIOS support ?
  3322. CLK_use_chip        db 0FFh ; Use add-on clock chip ?
  3323.                 ; (Exception to normal meaning:  0FEh means
  3324.                 ; command line said -C+ without saying
  3325.                 ; what type of chip.  Positive values are
  3326.                 ; ASCII chars saying what type of chip
  3327.                 ; will be used.)
  3328. CLK_use_BIOS        db 0FFh ; Use normal BIOS tick counter functions ?
  3329. CLK_give_long_help    db 0    ; Print long help message ?
  3330. CLK_were_errors     db 0    ; Were there any errors ?
  3331. CLK_no_command_line    db 1    ; Were there any command line options ?
  3332.  
  3333. ;
  3334. ; Messages displayed by the startup routine
  3335. ;
  3336. illegal_opt_msg     db '  Unrecognised option character ignored: "'
  3337.   illegal_opt_char    db 'X'    ; Correct char gets inserted here
  3338.         db '".'
  3339.         db 13,10
  3340. illegal_opt_msg_len    equ $ - illegal_opt_msg
  3341. short_help_msg        db 'Use "DEVICE=CLOCK.DEV -H" for help.',13,10
  3342. short_help_msg_len    equ $ - short_help_msg
  3343. long_help_msg        label byte
  3344.  db 'Use "DEVICE=CLOCK.DEV <options>" in the CONFIG.SYS file.',13,10
  3345.  db 'Enable option X with "-X", "-X+", "/X" or "/X+"; disable with "-X-", "/X-".',13,10
  3346.  db 'If option takes a value, use "-X=value" or "/X=value".',13,10
  3347.  db 'Options are:',13,10
  3348.  db '   -H       Display this help message.',13,10
  3349.  db '            (Use "-H-" to disable the short help message.)',13,10
  3350.  db '   -A       Use real-time clock support provided in AT-type BIOS.',13,10
  3351.  db '   -B       Use ordinary BIOS timer.',13,10
  3352.  db '   -Ctype=addr   Use clock chip at the specified hexadecimal '
  3353.  db    'I/O address.',13,10
  3354.  db '            Type is optional.  Use 1 for MM58167AN chip, 2 for MSM6242 '
  3355.  db    'chip.',13,10
  3356.  db '            Addr is optional.  If omitted, program will try to find '
  3357.  db    'chip.',13,10
  3358.  db 'Default: Program tries to use AT BIOS real time clock.  Failing that, '
  3359.  db    'it looks',13,10
  3360.  db 'for clock chips in several standard locations.  If that also '
  3361.  db    'fails,',13,10
  3362.  db 'it uses the ordinary BIOS timer (but fixes the MS-DOS 3.2 date '
  3363.  db    'change bug).',13,10
  3364.  db 'Note: If a clock chip is found, ordinary DOS date and time commands '
  3365.  db    'will',13,10
  3366.  db 'set the chip time.  No need for the AT SETUP program or for the TIMER '
  3367.  db    'or',13,10
  3368.  db 'SETCLOCK and GETCLOCK programs supplied with add-on clock cards.',13,10
  3369.  db 13,10
  3370. long_help_msg_len    equ $ - long_help_msg
  3371. no_AT_BIOS_msg    db '  AT-style clock BIOS support is unavailable.',13,10
  3372. no_AT_BIOS_msg_len    equ $ - no_AT_BIOS_msg
  3373. no_chip_default_msg db '  Cannot find clock chip at standard address.',13,10
  3374. no_chip_default_msg_len equ $ - no_chip_default_msg
  3375. no_chip_specified_msg db '  Cannot find clock chip at specified address.',13,10
  3376. no_chip_specified_msg_len equ $ - no_chip_specified_msg
  3377. AT_BIOS_msg    db '  DOS date and time operations will use AT BIOS '
  3378.     db 'real time clock.',13,10
  3379. AT_BIOS_msg_len equ $ - AT_BIOS_msg
  3380. chip_msg    db '  DOS date and time operations will use clock chip.'
  3381.     db 13,10
  3382. chip_msg_len    equ $ - chip_msg
  3383. BIOS_msg    db '  DOS date and time operations will use the standard '
  3384.     db 'BIOS timer.',13,10
  3385. BIOS_msg_len    equ    $ - BIOS_msg
  3386. time_now_msg    label byte
  3387.     db '  Current date and time: '
  3388.   msg_weekday    db 'Day '
  3389.   msg_day    db 'DD '
  3390.   msg_month    db 'Mmm '
  3391.   msg_year    db 'YYYY  '
  3392.   msg_hour    db 'hh:'
  3393.   msg_min    db 'mm:'
  3394.   msg_sec    db 'ss'
  3395.     db 13,10
  3396. time_now_msg_len equ $ - time_now_msg
  3397. month_names    db 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec '
  3398. day_names    db 'Sun Mon Tue Wed Thu Fri Sat '
  3399.  
  3400. startup_data    ends
  3401.  
  3402. startup_code    segment
  3403.  
  3404. CLK_init    proc near
  3405.     debug_message '{ init '
  3406. ;
  3407. ; Print an initialisation message
  3408. ;
  3409.         push cs             ; Make DS:SI point to string
  3410.         pop  ds             ;
  3411.         mov  si,offset cgroup:start_message
  3412.         mov  cx,start_message_len   ; Put the length into CX
  3413.         call display_message        ; Display it
  3414. ;
  3415. ; Make DS:SI point to the command line arguments
  3416. ;
  3417.         lds  bx,cs:[request_pointer]    ; Point to request header
  3418.     lds    si,ds:[bx].D_INIT_args        ; Point to command line
  3419.     cld        ; Make sure SI gets incremented
  3420. ;
  3421. ; The command line pointer points just after
  3422. ; the equals sign of the DEVICE=... line in the CONFIG.SYS file.
  3423. ;
  3424. ; Skip any initial white space, and the driver name itself.
  3425. ;
  3426.     call opt_skip_space    ; Ignore white space
  3427.     call    opt_ignore    ; and driver name
  3428. ;
  3429. ; Loop processing option characters.  Exit when end of line is reached.
  3430. ;
  3431. CLK_init_opt_loop:
  3432. ;
  3433. ; Skip white space, slash and minus signs.
  3434. ;
  3435.     call opt_skip_space
  3436.     call    opt_skip_slash_minus
  3437.     jcond jc,CLK_init_end_options    ; Exit if end of line
  3438. ;
  3439. ; Remember that there were command line options.
  3440. ;
  3441.     mov    cs:[CLK_no_command_line],0
  3442. ;
  3443. ; Get an option character.  Carry flag will be set if there are no more
  3444. ; options.  Otherwise, option character will be in AL.
  3445. ; The option character will be in upper-case.
  3446. ;
  3447.     call    opt_get_uppercase_char
  3448. if do_debug_writes
  3449.     jcond jc,CLK_init_end_options    ; Exit if end of line
  3450. else
  3451.     jc    CLK_init_end_options    ; Exit if end of line
  3452. endif
  3453.     debug_message '( option char '
  3454.     debug_show_char al
  3455.     debug_message ' ) '
  3456. ;
  3457. ; Process the option character
  3458. ;
  3459.     cmp    al,'H'    ; Help
  3460.     je    CLK_init_opt_H
  3461.     cmp    al,'A'    ; Use AT BIOS clock support
  3462.     je    CLK_init_opt_A
  3463.     cmp    al,'B'    ; Use normal BIOS timer
  3464.     je    CLK_init_opt_B
  3465.     cmp    al,'C'    ; Use clock chip
  3466.     je    CLK_init_opt_C
  3467. ;;;    cmp    al,'P'    ; Prompt user for initial time (not yet implemented)
  3468. ;;;    je    CLK_init_opt_P
  3469. CLK_init_opt_illegal:
  3470. ;
  3471. ; The option character was illegal.  Print a message and skip until
  3472. ; white space or a slash.
  3473. ;
  3474.     push    ds                ; Save DS:SI
  3475.     push    si
  3476.     push    cs                ; Make DS:SI point to message
  3477.     pop    ds
  3478.     mov    si,offset cgroup:illegal_opt_msg
  3479.     mov    cx,illegal_opt_msg_len        ; Message length in CX
  3480.     mov    ds:[illegal_opt_char],al    ; Shove the character into
  3481.                         ; the message
  3482.     call    display_message
  3483.     mov    [CLK_were_errors],1        ; Remember there were errors
  3484.     pop    si    ; Restore DS:SI
  3485.     pop    ds
  3486.     call    opt_ignore        ; Skip until white space or slash
  3487.     jmp    CLK_init_opt_loop    ; Process next character
  3488. CLK_init_opt_H:
  3489. ;
  3490. ; Check if it was H- or H+, and remember.
  3491. ;
  3492.     call    opt_get_plus_minus    ; Returns AL=1 for "+", 0 for "-"
  3493.     mov    cs:[CLK_give_long_help],al
  3494.     jmp    CLK_init_opt_loop    ; Process next character
  3495. CLK_init_opt_A:
  3496. ;
  3497. ; Check if it was A- or A+, and remember.
  3498. ;
  3499.     call    opt_get_plus_minus    ; Returns AL=1 for "+", 0 for "-"
  3500.     mov    cs:[CLK_use_AT_BIOS],al
  3501.     jmp    CLK_init_opt_loop    ; Process next character
  3502. CLK_init_opt_B:
  3503. ;
  3504. ; Check if it was B- or B+, and remember.
  3505. ;
  3506.     call    opt_get_plus_minus    ; Returns AL=1 for "+", 0 for "-"
  3507.     mov    cs:[CLK_use_BIOS],al
  3508.     jmp    CLK_init_opt_loop    ; Process next character
  3509. CLK_init_opt_C:
  3510. ;
  3511. ; Start by assuming it was -C+
  3512. ;
  3513.     mov    cs:[CLK_use_chip], 0FEh
  3514. ;
  3515. ; Check for a number immediately after the C (tells what type of chip).
  3516. ; Note: this bypasses opt_get_char etc.
  3517. ;
  3518.     mov    al, ds:[si]        ; next char (just peek)
  3519.     cmp    al, '1'         ; is it in ['1'..'2'] ?
  3520.     jb    CLK_init_opt_C_nonum    ; no
  3521.     cmp    al, '2'
  3522.     ja    CLK_init_opt_C_nonum    ; no
  3523.     mov    cs:[CLK_use_chip], al    ; yes, remember it
  3524.     inc    si            ; and skip past character
  3525. CLK_init_opt_C_nonum:
  3526. ;
  3527. ; Check if it was C- or C+ or C=
  3528. ;
  3529.     call    opt_get_plus_minus_eq    ; Returns AL=1 for "+", 0 for "-"
  3530.             ; AL = 2 for "="
  3531.     or    al, al                ; Was it "C-" ?
  3532.     jne    CLK_init_opt_C_notminus
  3533.     mov    cs:[CLK_use_chip], al        ; Yes, clear flag
  3534.     jmp    CLK_init_opt_loop        ; and process nect option
  3535. CLK_init_opt_C_notminus:
  3536.     cmp    al,2                ; Was it "C=" ?
  3537.     jcond    jne, CLK_init_opt_loop        ; No, process next option
  3538. CLK_init_opt_C_equals:
  3539.     call    opt_get_hex_word        ; Yes, get the address
  3540.     mov    cs:[CLK_chip_IO_base],dx
  3541.     jmp    CLK_init_opt_loop        ; Process next option
  3542. CLK_init_end_options:
  3543. ;
  3544. ; Finished reading the options from the command line.
  3545. ; Now print help message if necessary.
  3546. ;
  3547.     push    cs    ; Make DS point to the right place
  3548.     pop    ds
  3549.     test    [CLK_give_long_help],0FFh    ; Long help message ?
  3550.     jz    CLK_init_no_long_help
  3551.     mov    si, offset cgroup:long_help_msg
  3552.     mov    cx,long_help_msg_len
  3553.     call    display_message
  3554.     jmp    CLK_init_end_help
  3555. CLK_init_no_long_help:
  3556.     mov    al,[CLK_no_command_line]    ; Short help message ?
  3557.     or    al,[CLK_were_errors]        ;
  3558.     jz    CLK_init_end_help        ;
  3559.     mov    si, offset cgroup:short_help_msg
  3560.     mov    cx,short_help_msg_len
  3561.     call    display_message
  3562. CLK_init_end_help:
  3563. ;
  3564. ; Check for AT BIOS clock support.
  3565. ;
  3566.     debug_message '{ test for AT BIOS : '
  3567.     test    [CLK_use_AT_BIOS],0FFh        ; Don't even try if told not to
  3568.     jz    CLK_init_end_AT_BIOS        ;
  3569.     call    CLK_find_AT_BIOS        ; Try to use AT BIOS
  3570.     jc    CLK_init_no_AT_BIOS        ; Jump if not found
  3571.     mov    [CLK_use_AT_BIOS],1        ; Remember it is available
  3572.     jmp    CLK_init_end_AT_BIOS        ;
  3573. CLK_init_no_AT_BIOS:
  3574. ;
  3575. ; If user specified AT BIOS support but there is none, print error message.
  3576. ;
  3577.     mov    al,[CLK_use_AT_BIOS]        ; See what user asked for
  3578.     mov    [CLK_use_AT_BIOS],0        ; Remember not to use AT BIOS
  3579.     test    al,al                ; Error if user said it
  3580.     jl    CLK_init_end_AT_BIOS        ; ... was available
  3581.     mov    si, offset cgroup:no_AT_BIOS_msg
  3582.     mov    cx,no_AT_BIOS_msg_len
  3583.     call    display_message
  3584. CLK_init_end_AT_BIOS:
  3585.     debug_hex_byte [CLK_use_AT_BIOS]
  3586.     debug_message '} '
  3587. ;
  3588. ; Check for clock chip support.
  3589. ;
  3590.     debug_message '{ test for clock chip : '
  3591.     mov    al,[CLK_use_chip]        ; Check what to do
  3592.     or    al, al                ; Don't even try if told not to
  3593.     jz    CLK_init_end_chip
  3594.     call    CLK_find_chip            ; Look for the clock chip
  3595.     jc    CLK_init_no_chip        ; Jump if not found
  3596.     mov    [CLK_use_chip],al        ; Remember it is available
  3597.     jmp    CLK_init_end_chip
  3598. CLK_init_no_chip:
  3599. ;
  3600. ; If user specified clock chip support but there is none, print error message.
  3601. ;
  3602.     mov    al,[CLK_use_chip]        ; See what user said
  3603.     mov    [CLK_use_chip],0        ; Remember not to use chip
  3604.     cmp    al, 0FFh            ; FF means user said nothing
  3605.     je    CLK_init_end_chip        ; so remain silent
  3606.     test    al,al                ; FFFE means user said -C+
  3607.     jl    CLK_init_no_default_chip    ; "no chip at default addr"
  3608.                         ; else "no chip at spec addr"
  3609.     mov    si, offset cgroup:no_chip_specified_msg ; if "-C=xxxx"
  3610.     mov    cx,no_chip_specified_msg_len
  3611.     call    display_message
  3612.     jmp    short CLK_init_end_chip
  3613. CLK_init_no_default_chip:
  3614.     mov    si, offset cgroup:no_chip_default_msg    ; if "-C+" but no chip
  3615.     mov    cx,no_chip_default_msg_len
  3616.     call    display_message
  3617. CLK_init_end_chip:
  3618.     debug_hex_word [CLK_chip_IO_base]
  3619.     debug_message '} '
  3620. ;
  3621. ; If neither AT BIOS nor clock chip support is available, use normal BIOS.
  3622. ;
  3623.     mov    al,[CLK_use_AT_BIOS]        ; Use AT BIOS ?
  3624.     xor    ah,ah
  3625.     or    ax,[CLK_chip_IO_base]        ; Or use clock chip ?
  3626.     jnz    CLK_init_end_BIOS        ; OK if one or the other
  3627.     debug_message '{ Must use normal BIOS } '
  3628.     mov    [CLK_use_BIOS],1        ; Remember to use normal BIOS
  3629. CLK_init_end_BIOS:
  3630. ;
  3631. ; Store pointers to suitable clock get and set procedures into the
  3632. ; area used by CLK_fix_our_time and CLK_set_other_timers.
  3633. ;
  3634. ; If there is AT BIOS support then
  3635. ;     Use AT BIOS
  3636. ; Else If clock chip support then
  3637. ;     Use clock chip, but make plain BIOS time match chip time
  3638. ; Else (must be just plain BIOS support)
  3639. ;    Use only plain BIOS
  3640. ;
  3641. ; Instal AT BIOS access procedures.
  3642. ;
  3643.     test    [CLK_use_AT_BIOS],0FFh        ; Use AT BIOS ?
  3644.     jz    CLK_init_store_no_AT_BIOS
  3645.                         ; Yes, use AT BIOS
  3646.     debug_message '( Installing AT BIOS procedures ) '
  3647.     mov    [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
  3648.     mov    [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_AT_BIOS_time
  3649.     mov    [CLK_set_procedure_ptr], offset cgroup:CLK_set_AT_BIOS_time
  3650.     mov    si,offset cgroup:AT_BIOS_msg    ; Print message
  3651.     mov    cx,AT_BIOS_msg_len
  3652.     call    display_message
  3653.     jmp    CLK_init_store_end
  3654. CLK_init_store_no_AT_BIOS:
  3655. ;
  3656. ; Instal clock chip access procedures.
  3657. ;
  3658.     test    [CLK_use_chip],0FFh        ; Use clock chip ?
  3659.     jz    CLK_init_store_no_chip
  3660.                         ; Yes, use chip
  3661.     debug_message '( Installing clock chip [type '
  3662.     debug_hex_byte [CLK_use_chip]
  3663.     debug_message '] procedures ) '
  3664.     mov    si,offset cgroup:chip_msg   ; Print message
  3665.     mov    cx,chip_msg_len
  3666.     call    display_message
  3667.     cmp    [CLK_use_chip],'1'        ; MM58167AN chip?
  3668.     je    CLK_init_store_MM58167AN
  3669.     cmp    [CLK_use_chip],'2'        ; MSM6242 chip?
  3670.     je    CLK_init_store_MSM6242
  3671.     ; should never get here, but just in case...
  3672.     debug_message '*** Internal error : Invalid chip type *** '
  3673.     jmp    CLK_init_store_no_chip        ; must have been mistaken!
  3674. CLK_init_store_MM58167AN:
  3675.     mov    [CLK_get_procedure_ptr], offset cgroup:CLK_get_MM58167AN_time
  3676.     mov    [CLK_set_procedure_ptr], offset cgroup:CLK_set_MM58167AN_time
  3677.     jmp    CLK_init_store_end
  3678. CLK_init_store_MSM6242:
  3679.     mov    [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_or_other_time
  3680.     mov    [CLK_get_other_procedure_ptr], offset cgroup:CLK_get_MSM6242_time
  3681.     mov    [CLK_set_procedure_ptr], offset cgroup:CLK_set_MSM6242_time
  3682.     jmp    CLK_init_store_end
  3683. CLK_init_store_no_chip:
  3684. ;
  3685. ; Instal plain BIOS access procedures.
  3686. ;
  3687.     debug_message '( Installing plain BIOS procedures ) '
  3688.     mov    si,offset cgroup:BIOS_msg    ; Print message
  3689.     mov    cx,BIOS_msg_len
  3690.     call    display_message
  3691.     mov    [CLK_get_procedure_ptr], offset cgroup:CLK_get_BIOS_time
  3692.     mov    [CLK_set_procedure_ptr], offset cgroup:null_proc
  3693. CLK_init_store_end:
  3694. ;
  3695. ; Get the current date and time.
  3696. ; Ensure that year, month, day and weekday are known.
  3697. ;
  3698.     call    CLK_fix_our_time    ; Get the time from somewhere
  3699.     call    CLK_set_BIOS_time    ; Make plain BIOS time match it
  3700.                     ; (no harm done if time came from BIOS)
  3701.     call    CLK_conv_days_ccyymmdd    ; Convert to year, month, day
  3702.     call    CLK_conv_days_weekday    ; Find the day of the week
  3703. ;
  3704. ; Format the date and time for printing
  3705. ;
  3706.     mov    al,[CLK_date_century]        ; Century
  3707.     call    conv_binary_ASCII_decimal
  3708.     mov    word ptr [msg_year],ax
  3709.     mov    al,[CLK_date_year]        ; Year
  3710.     call    conv_binary_ASCII_decimal
  3711.     mov    word ptr [msg_year+2],ax
  3712.     mov    bl,[CLK_date_month]        ; Month
  3713.     dec    bl                ; Subtract 1 and multiply by 4
  3714.     shl    bl,1                ; to get offset into table
  3715.     shl    bl,1
  3716.     xor    bh,bh
  3717.     mov    ax, word ptr [month_names][bx]    ; Copy 4 bytes
  3718.     mov    word ptr [msg_month],ax
  3719.     mov    ax, word ptr [month_names][bx+2]
  3720.     mov    word ptr [msg_month+2],ax
  3721.     mov    al,[CLK_date_day]        ; Day of month
  3722.     call    conv_binary_ASCII_decimal
  3723.     mov    word ptr [msg_day],ax
  3724.     mov    bl,[CLK_date_weekday]        ; Day of week
  3725.     shl    bl,1    ; Multiply by 4
  3726.     shl    bl,1    ; to get offset into table
  3727.     mov    ax, word ptr [day_names][bx]    ; Copy 4 bytes
  3728.     mov    word ptr [msg_weekday],ax
  3729.     mov    ax,word ptr [day_names][bx+2]
  3730.     mov    word ptr [msg_weekday+2],ax
  3731.     mov    al,[CLK_time_hour]        ; Hour
  3732.     call    conv_binary_ASCII_decimal
  3733.     mov    word ptr [msg_hour],ax
  3734.     mov    al,[CLK_time_min]        ; Minute
  3735.     call    conv_binary_ASCII_decimal
  3736.     mov    word ptr [msg_min],ax
  3737.     mov    al,[CLK_time_sec]        ; Second
  3738.     call    conv_binary_ASCII_decimal
  3739.     mov    word ptr [msg_sec],ax
  3740. ;
  3741. ; Print the date and time.
  3742. ;
  3743.     mov    si,offset cgroup:time_now_msg
  3744.     mov    cx,time_now_msg_len
  3745.     call    display_message
  3746. ;
  3747. ; Return address of end of resident memory.
  3748. ; Note: The IBM manual says it is the address of the first byte to be freed,
  3749. ; while the Microsoft manual says it is the address of the last byte to be
  3750. ; retained.  Play safe and return the address of the first byte to be freed.
  3751. ;
  3752.         mov  ax, offset cgroup:end_resident_memory
  3753.                     ; This is the first byte to be freed
  3754.         lds  bx,cs:[request_pointer] ; Make DS:BX point to request
  3755.         mov  word ptr ds:[bx].D_INIT_end, ax ; Return offset
  3756.         mov  ax,cs                 ; Also return segment
  3757.         mov  word ptr ds:[bx].D_INIT_end[2], ax
  3758.         jmp  exit_ok
  3759. CLK_init     endp
  3760.  
  3761. startup_code    ends
  3762.  
  3763. ;***********************************************************************
  3764.  
  3765. ;
  3766. ; Convert a byte to two ASCII decimal digits.
  3767. ; The input binary calue (in AL) must be less than 100.
  3768. ; The two bytes will be in AH (least significant) and AL (most significant).
  3769. ; The bytes are in this order to allow a "MOV [memory],AX" instruction
  3770. ; to put the high digit first.
  3771. ;
  3772.  
  3773. resident_code    segment
  3774.  
  3775. conv_binary_ASCII_decimal proc near
  3776.     aam                ; AH will be high digit
  3777.                     ; AL will be low digit
  3778.     add    ax,'00'         ; Convert to ASCII digits
  3779.     xchg    ah,al            ; Swap the order
  3780.     ret
  3781. conv_binary_ASCII_decimal endp
  3782.  
  3783. resident_code    ends
  3784.  
  3785. ;***********************************************************************
  3786.  
  3787. ;
  3788. ; Some subroutines concerned with command line option processing.
  3789. ; Called with DS:SI pointing to the next character in the command line.
  3790. ; They exit with the carry flag set when the end of the option line
  3791. ; is reached.
  3792. ;
  3793.  
  3794. startup_code    segment
  3795.  
  3796. ;
  3797. ; Get a single character
  3798. ;
  3799.  
  3800. opt_get_char    proc    near
  3801. ;
  3802. ; Get character
  3803. ;
  3804.     lodsb                ; Get a character
  3805.     cmp    al,13            ; Is it the end of the line ?
  3806.     je    opt_get_char_end_line
  3807.     cmp    al,10
  3808.     je    opt_get_char_end_line
  3809. ;
  3810. ; Exit with carry clear if not end of line.
  3811. ;
  3812.     debug_message '( get_opt_char "'
  3813.     debug_show_char al
  3814.     debug_message '" hex '
  3815.     debug_hex_byte al
  3816.     debug_message ' ) '
  3817.     clc
  3818.     ret
  3819. ;
  3820. ; Decrement the pointer and exit with carry flag set if end of line
  3821. ;
  3822. opt_get_char_end_line:
  3823.     debug_message '( get_opt_char end of line ) '
  3824.     dec    si            ; Undo the pointer increment
  3825.     stc
  3826.     ret
  3827. opt_get_char    endp
  3828.  
  3829. ;
  3830. ; Get a single character, and convert it to upper-case.
  3831. ;
  3832.  
  3833. opt_get_uppercase_char proc near
  3834. ;
  3835. ; Get char and exit if end of line
  3836. ;
  3837.     call    opt_get_char
  3838.     jc    opt_uppercase_end
  3839. ;
  3840. ; Convert to uppercase and clear carry flag
  3841. ;
  3842.     cmp    al,'a'
  3843.     jb    opt_uppercase_OK    ; It was not a lowercase char
  3844.     cmp    al,'z'
  3845.     ja    opt_uppercase_OK    ; It was not a lowercase char
  3846.     add    al,'A'-'a'        ; Convert it to uppercase
  3847. opt_uppercase_OK:
  3848.     clc
  3849. opt_uppercase_end:
  3850.     ret
  3851. opt_get_uppercase_char endp
  3852.  
  3853. ;
  3854. ; Skip leading white space.
  3855. ;
  3856.  
  3857. opt_skip_space    proc near
  3858. ;
  3859. ; Get char and exit if end of line.
  3860. ; Loop if it is a white space character.
  3861. ; Exit with pointer at next non-white space character.
  3862. ;
  3863. ; Note: a NUL is treated as a white space character.  This is because MS-DOS
  3864. ; version 2 puts a NUL after the driver file name in the command line it
  3865. ; passes to the device driver initialisation routine.
  3866. ;
  3867. opt_skip_space_loop:
  3868.     call    opt_get_char
  3869.     jc    opt_skip_end
  3870.     cmp    al,' '            ; Is it a space ?
  3871.     je    opt_skip_space_loop
  3872.     cmp    al,9            ; Is it a TAB ?
  3873.     je    opt_skip_space_loop
  3874.     cmp    al,0            ; What about a NUL ?
  3875.     je    opt_skip_space_loop
  3876. ;
  3877. ; Decrement pointer and exit with carry clear if all is OK
  3878. ;
  3879.     dec    si
  3880.     clc
  3881. opt_skip_end:
  3882.     ret
  3883. opt_skip_space    endp
  3884.  
  3885. ;
  3886. ; Skip slash or minus sign before option character.
  3887. ;
  3888.  
  3889. opt_skip_slash_minus proc near
  3890. ;
  3891. ; Get char and exit if end of line.
  3892. ; Un-get the char if not a slash or minus sign.
  3893. ;
  3894.     call    opt_get_char
  3895.     jc    opt_skip_slash_end
  3896.     cmp    al,'/'            ; A slash ?
  3897.     je    opt_skip_slash_OK
  3898.     cmp    al,'-'            ; Or a dash ?
  3899.     je    opt_skip_slash_OK
  3900. ;
  3901. ; Decrement pointer and exit with carry clear if not a slash or dash
  3902. ;
  3903.     dec    si
  3904. opt_skip_slash_OK:
  3905.     clc
  3906. opt_skip_slash_end:
  3907.     ret
  3908. opt_skip_slash_minus endp
  3909.  
  3910. ;
  3911. ; Ignore everything until next white space character.
  3912. ;
  3913.  
  3914. opt_ignore    proc near
  3915. ;
  3916. ; Get char and exit if end of line.
  3917. ; Exit if it is a white space character; else loop.
  3918. ;
  3919. ; Note: a NUL is treated as a white space character.  This is because MS-DOS
  3920. ; version 2 puts a NUL after the driver file name in the command line it
  3921. ; passes to the device driver initialisation routine.
  3922. ;
  3923. opt_ignore_loop:
  3924.     call    opt_get_char
  3925.     jc    opt_ignore_end
  3926.     cmp    al,' '            ; Is it a space ?
  3927.     je    opt_ignore_OK
  3928.     cmp    al,9            ; Is it a TAB ?
  3929.     je    opt_ignore_OK
  3930.     cmp    al,0            ; What about a NUL ?
  3931.     je    opt_ignore_OK
  3932.     jmp    opt_ignore_loop     ; Loop for next char
  3933. ;
  3934. ; Decrement pointer and exit with carry clear if all is OK
  3935. ;
  3936. opt_ignore_OK:
  3937.     dec    si
  3938.     clc
  3939. opt_ignore_end:
  3940.     ret
  3941. opt_ignore    endp
  3942.  
  3943. ;
  3944. ; Get plus or minus sign (after an option character).
  3945. ; Behave as if a plus sign was present if there was no plus or minus.
  3946. ; Return AL=1 for plus, AL=0 for minus.
  3947. ;
  3948.  
  3949. opt_get_plus_minus proc near
  3950. ;
  3951. ; Get char and exit if end of line
  3952. ;
  3953.     call    opt_get_char
  3954.     mov    ah,al            ; Save char
  3955.     mov    al,1            ; Assume "+"
  3956.     jc    opt_plus_minus_end    ; Exit if end of line
  3957. ;
  3958. ; Check for plus or minus sign.  Decrement pointer if neither.
  3959. ;
  3960.     xor    al,al            ; Ready in case it is a '-'
  3961.     cmp    ah,'-'
  3962.     je    opt_plus_minus_OK
  3963.     mov    al,1            ; Ready in case it is a '+'
  3964.     cmp    ah,'+'
  3965.     je    opt_plus_minus_OK
  3966.     dec    si            ; Decrement pointer if neither
  3967. opt_plus_minus_OK:
  3968.     clc
  3969. opt_plus_minus_end:
  3970.     ret
  3971. opt_get_plus_minus endp
  3972.  
  3973. ;
  3974. ; Get plus, minus or equals sign (after an option character).
  3975. ; Behave as if a plus sign was present if there was nothing.
  3976. ; Return AL=1 for plus, AL=0 for minus, AL=2 for equals.
  3977. ;
  3978.  
  3979. opt_get_plus_minus_eq proc near
  3980. ;
  3981. ; Get char and exit if end of line
  3982. ;
  3983.     call    opt_get_char
  3984.     mov    ah,al            ; Save char
  3985.     mov    al,1            ; Assume "+"
  3986.     jc    opt_plus_minus_eq_end    ; Exit if end of line
  3987. ;
  3988. ; Check for plus or minus sign.  Decrement pointer if neither.
  3989. ;
  3990.     xor    al,al            ; Ready in case it is a '-'
  3991.     cmp    ah,'-'
  3992.     je    opt_plus_minus_eq_OK
  3993.     mov    al,2            ; Ready in case it is an '='
  3994.     cmp    ah,'='
  3995.     je    opt_plus_minus_eq_OK
  3996.     mov    al,1            ; Ready in case it is a '+'
  3997.     cmp    ah,'+'
  3998.     je    opt_plus_minus_eq_OK
  3999.     dec    si            ; Decrement pointer if neither
  4000. opt_plus_minus_eq_OK:
  4001.     clc
  4002. opt_plus_minus_eq_end:
  4003.     ret
  4004. opt_get_plus_minus_eq endp
  4005.  
  4006. ;
  4007. ; Get a hexadecimal digit.
  4008. ; Exit with carry flag set if end of line,
  4009. ; or with  zero flag set if no more hexadecimal digits,
  4010. ; or with value in AL if valid digit found.
  4011. ;
  4012.  
  4013. opt_get_hex_digit proc near
  4014. ;
  4015. ; Get char and exit if end of line
  4016. ;
  4017.     call    opt_get_uppercase_char
  4018.     jc    opt_hex_digit_end
  4019. ;
  4020. ; Check for hex digit.
  4021. ;
  4022.     sub    al,'0'
  4023.     jl    opt_hex_digit_bad
  4024.     cmp    al,9
  4025.     jle    opt_hex_digit_OK
  4026.     sub    al,'A'-'0'
  4027.     jl    opt_hex_digit_bad
  4028.     add    al,0Ah
  4029.     cmp    al,0Fh
  4030.     jle    opt_hex_digit_OK
  4031. ;
  4032. ; Decrement pointer and exit with carry clear and zero flag set for bad digit.
  4033. ;
  4034. opt_hex_digit_bad:
  4035.     dec    si
  4036.     test    al,0            ; Set zero flag and clear carry
  4037. opt_hex_digit_end:
  4038.     ret
  4039. ;
  4040. ; Exit with carry and zero clear if no problem
  4041. ;
  4042. opt_hex_digit_OK:
  4043.     cmp    al,0FFh         ; Clear the zero flag
  4044.     clc                ; Clear the carry flag
  4045.     ret
  4046. opt_get_hex_digit endp
  4047.  
  4048. ;
  4049. ; Get hexadecimal word (after an option character).
  4050. ; Value is returned in DX.
  4051. ;
  4052.  
  4053. opt_get_hex_word proc near
  4054. ;
  4055. ; Start with a value of zero.
  4056. ; Loop until a non-hex character is found.
  4057. ;
  4058.     xor    dx,dx
  4059. opt_hex_word_loop:
  4060. ;
  4061. ; Get a character and exit if end of line or if no more digits.
  4062. ;
  4063.     call opt_get_hex_digit
  4064.     jc    opt_hex_word_end    ; End of line if carry flag set
  4065.     jz    opt_hex_word_OK     ; No more digits if zero flag set
  4066. ;
  4067. ; Shift previous result, add new digit, and loop for next digit.
  4068. ;
  4069.     mov    cl,4
  4070.     shl    dx,cl
  4071.     xor    ah,ah
  4072.     add    dx,ax
  4073.     jmp    opt_hex_word_loop
  4074. ;
  4075. ; Exit with no carry if completed successfully.
  4076. ;
  4077. opt_hex_word_OK:
  4078.     clc
  4079. opt_hex_word_end:
  4080.     ret
  4081. opt_get_hex_word endp
  4082.  
  4083. startup_code    ends
  4084.  
  4085. ;***********************************************************************
  4086.  
  4087. if add_test_code
  4088.  
  4089. ;
  4090. ; This code is intended to make debugging easier.
  4091. ; If the program is compiled with the add_test_code option, the resulting
  4092. ; .EXE program will not be convertible by EXE2BIN, but can be run as an
  4093. ; ordinary program.  It will pass its command line arguments to the
  4094. ; device driver initialisation routine, and will also perform a device
  4095. ; read and write operation, before terminating.
  4096. ; If the program is run under the control of a debugger, passing suitable
  4097. ; arguments to the device driver can be accomplished by modifying the
  4098. ; data in the init_request, read_request and write_request buffers.
  4099. ;
  4100.  
  4101. startup_data    segment
  4102.  
  4103. ;
  4104. ; The command line options passed to the initialisation routine
  4105. ;
  4106. command_line    db    128 dup (13)
  4107.  
  4108. ;
  4109. ; The date and time, in the format used by the device driver read and write
  4110. ; operations.
  4111. ;
  4112. buffer    db    6 dup (?)
  4113.  
  4114. ;
  4115. ; A properly formatted initialisation request.
  4116. ;
  4117. init_request    label byte
  4118.   init_length    db    23
  4119.   init_unit    db    0
  4120.   init_command    db    0    ; INIT
  4121.   init_status    dw    ?
  4122.   init_reserv    db    8 dup (0)
  4123.   init_units    db    0
  4124.   init_end    dd    ?
  4125.   init_bpb    dd    cgroup:command_line
  4126.   init_blocknum db    0
  4127.  
  4128. ;
  4129. ; A properly formatted read request.
  4130. ;
  4131. read_request    label byte
  4132.   read_length    db    26
  4133.   read_unit    db    0
  4134.   read_command    db    4    ; READ
  4135.   read_status    dw    ?
  4136.   read_reserv    db    8 dup (0)
  4137.   read_media    db    0
  4138.   read_address    dd    cgroup:buffer
  4139.   read_datalen    dw    6
  4140.   read_start    dw    0
  4141.   read_volume    dd    ?
  4142.  
  4143. ;
  4144. ; A properly formatted write request.
  4145. ;
  4146. write_request    label byte
  4147.   write_length    db    26
  4148.   write_unit    db    0
  4149.   write_command db    8    ; WRITE
  4150.   write_status    dw    ?
  4151.   write_reserv    db    8 dup (0)
  4152.   write_media    db    0
  4153.   write_address dd    cgroup:buffer
  4154.   write_datalen dw    6
  4155.   write_start    dw    0
  4156.   write_volume    dd    ?
  4157.  
  4158. startup_data    ends
  4159.  
  4160. startup_code    segment
  4161.  
  4162. ;
  4163. ; Main entry point for testing.
  4164. ;
  4165.  
  4166. test_proc    proc near
  4167. test_start:
  4168. ;
  4169. ; Copy command line parameters to buffer area.
  4170. ;
  4171.     mov    si,0081h
  4172.     push    cs
  4173.     pop    es
  4174.     mov    di,offset cgroup:command_line
  4175.     cld
  4176.     mov    cx,127
  4177.     rep movsb
  4178. test_init:
  4179. ;
  4180. ; Initialise.
  4181. ;
  4182.     mov    bx,offset cgroup:init_request
  4183.     call    test_call
  4184. test_read:
  4185. ;
  4186. ; Read.
  4187. ;
  4188.     mov    bx,offset cgroup:read_request
  4189.     call    test_call
  4190. test_write:
  4191. ;
  4192. ; Write.
  4193. ;
  4194.     mov    bx,offset cgroup:write_request
  4195.     call    test_call
  4196. test_exit:
  4197. ;
  4198. ; Exit.
  4199. ;
  4200.     mov    ax,4C00h
  4201.     int    21h
  4202. test_proc    endp
  4203.  
  4204. test_call    proc near
  4205.     push    cs        ; Make ES:BX point to request area
  4206.     pop    es
  4207.     nop
  4208.     call    CLK_strategy    ; Call strategy and interrupt
  4209.     call    CLK_interrupt
  4210.     ret
  4211. test_call    endp
  4212.  
  4213. startup_code    ends
  4214.  
  4215. endif ; add_test_code
  4216.  
  4217. ;***********************************************************************
  4218.  
  4219. ; Ugly, isn't it?
  4220.  
  4221. if add_test_code
  4222.  end_statement equ <end cgroup:test_start>
  4223. else
  4224.  end_statement equ <end>
  4225. endif
  4226.  
  4227.     end_statement
  4228.